Merge "[Inline Reply] Cover heads up and pinned in isExpanded" into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 9e2872f..1d40c53 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -105,6 +105,7 @@
     field public static final String CAMERA_OPEN_CLOSE_LISTENER = "android.permission.CAMERA_OPEN_CLOSE_LISTENER";
     field @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") public static final String CAMERA_PRIVACY_ALLOWLIST = "android.permission.CAMERA_PRIVACY_ALLOWLIST";
     field public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD";
+    field @FlaggedApi("android.os.allow_consentless_bugreport_delegated_consent") public static final String CAPTURE_CONSENTLESS_BUGREPORT_DELEGATED_CONSENT = "android.permission.CAPTURE_CONSENTLESS_BUGREPORT_DELEGATED_CONSENT";
     field public static final String CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD = "android.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD";
     field public static final String CAPTURE_MEDIA_OUTPUT = "android.permission.CAPTURE_MEDIA_OUTPUT";
     field public static final String CAPTURE_TUNER_AUDIO_INPUT = "android.permission.CAPTURE_TUNER_AUDIO_INPUT";
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 497d47a..87acbbf 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -385,7 +385,7 @@
     /**
      * @hide
      */
-    public static final int CACHE_USER_DATA_SIZE = 4;
+    public static final int CACHE_USER_DATA_SIZE = 32;
 
     private static final class AccountKeyData {
         final public Account account;
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 5956e2b..80764af 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1128,7 +1128,7 @@
     private static final PropertyInvalidatedCache<Integer, GetPackagesForUidResult>
             mGetPackagesForUidCache =
             new PropertyInvalidatedCache<Integer, GetPackagesForUidResult>(
-                32, CACHE_KEY_PACKAGES_FOR_UID_PROPERTY) {
+                1024, CACHE_KEY_PACKAGES_FOR_UID_PROPERTY) {
                 @Override
                 public GetPackagesForUidResult recompute(Integer uid) {
                     try {
diff --git a/core/java/android/app/compat/ChangeIdStateCache.java b/core/java/android/app/compat/ChangeIdStateCache.java
index dea4e9c8..7948cec 100644
--- a/core/java/android/app/compat/ChangeIdStateCache.java
+++ b/core/java/android/app/compat/ChangeIdStateCache.java
@@ -32,7 +32,7 @@
 public final class ChangeIdStateCache
         extends PropertyInvalidatedCache<ChangeIdStateQuery, Boolean> {
     private static final String CACHE_KEY = "cache_key.is_compat_change_enabled";
-    private static final int MAX_ENTRIES = 64;
+    private static final int MAX_ENTRIES = 2048;
     private static boolean sDisabled = false;
     private volatile IPlatformCompat mPlatformCompat;
 
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 9a04ded..ba3f3c4 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -89,12 +89,10 @@
      * @param item A single message that can contain a client activity/window request/callback.
      */
     public void addTransactionItem(@NonNull ClientTransactionItem item) {
-        if (Flags.bundleClientTransactionFlag()) {
-            if (mTransactionItems == null) {
-                mTransactionItems = new ArrayList<>();
-            }
-            mTransactionItems.add(item);
+        if (mTransactionItems == null) {
+            mTransactionItems = new ArrayList<>();
         }
+        mTransactionItems.add(item);
 
         // TODO(b/324203798): cleanup after remove UnsupportedAppUsage
         // Populate even if mTransactionItems is set to support the UnsupportedAppUsage.
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index 9b53461..0c1e7a3 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -20,7 +20,6 @@
 import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.window.flags.Flags.activityWindowInfoFlag;
-import static com.android.window.flags.Flags.bundleClientTransactionFlag;
 
 import static java.util.Objects.requireNonNull;
 
@@ -196,7 +195,7 @@
 
     /** Called before updating the Configuration of the given {@code context}. */
     public void onContextConfigurationPreChanged(@NonNull Context context) {
-        if (!bundleClientTransactionFlag() || ActivityThread.isSystem()) {
+        if (ActivityThread.isSystem()) {
             // Not enable for system server.
             return;
         }
@@ -212,7 +211,7 @@
 
     /** Called after updating the Configuration of the given {@code context}. */
     public void onContextConfigurationPostChanged(@NonNull Context context) {
-        if (!bundleClientTransactionFlag() || ActivityThread.isSystem()) {
+        if (ActivityThread.isSystem()) {
             // Not enable for system server.
             return;
         }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 282ede3..8c56a9d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -11436,7 +11436,7 @@
     private static final PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>
             sApplicationInfoCache =
             new PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>(
-                    32, PermissionManager.CACHE_KEY_PACKAGE_INFO,
+                    2048, PermissionManager.CACHE_KEY_PACKAGE_INFO,
                     "getApplicationInfo") {
                 @Override
                 public ApplicationInfo recompute(ApplicationInfoQuery query) {
@@ -11537,7 +11537,7 @@
     private static final PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>
             sPackageInfoCache =
             new PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>(
-                    64, PermissionManager.CACHE_KEY_PACKAGE_INFO,
+                    2048, PermissionManager.CACHE_KEY_PACKAGE_INFO,
                     "getPackageInfo") {
                 @Override
                 public PackageInfo recompute(PackageInfoQuery query) {
diff --git a/core/java/android/os/SharedMemory.java b/core/java/android/os/SharedMemory.java
index cba4423..c801fabf 100644
--- a/core/java/android/os/SharedMemory.java
+++ b/core/java/android/os/SharedMemory.java
@@ -25,6 +25,8 @@
 
 import dalvik.system.VMRuntime;
 
+import libcore.io.IoUtils;
+
 import java.io.Closeable;
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -63,7 +65,7 @@
 
         mMemoryRegistration = new MemoryRegistration(mSize);
         mCleaner = Cleaner.create(mFileDescriptor,
-                new Closer(mFileDescriptor.getInt$(), mMemoryRegistration));
+                new Closer(mFileDescriptor, mMemoryRegistration));
     }
 
     /**
@@ -276,7 +278,6 @@
      */
     @Override
     public void close() {
-        mFileDescriptor.setInt$(-1);
         if (mCleaner != null) {
             mCleaner.clean();
             mCleaner = null;
@@ -326,21 +327,20 @@
      * Cleaner that closes the FD
      */
     private static final class Closer implements Runnable {
-        private int mFd;
+        private FileDescriptor mFd;
         private MemoryRegistration mMemoryReference;
 
-        private Closer(int fd, MemoryRegistration memoryReference) {
+        private Closer(FileDescriptor fd, MemoryRegistration memoryReference) {
             mFd = fd;
+            IoUtils.setFdOwner(mFd, this);
             mMemoryReference = memoryReference;
         }
 
         @Override
         public void run() {
-            try {
-                FileDescriptor fd = new FileDescriptor();
-                fd.setInt$(mFd);
-                Os.close(fd);
-            } catch (ErrnoException e) { /* swallow error */ }
+            IoUtils.closeQuietly(mFd);
+            mFd = null;
+
             mMemoryReference.release();
             mMemoryReference = null;
         }
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index e82c4b0..76cf901 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -201,3 +201,10 @@
      description: "Tracing using Perfetto SDK."
      bug: "303199244"
 }
+
+flag {
+    name: "allow_consentless_bugreport_delegated_consent"
+    namespace: "crumpet"
+    description: "Allow privileged apps to call bugreport generation without enforcing user consent and delegate it to the calling app instead"
+    bug: "324046728"
+}
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index fe7eab7..3954bc2 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -27,14 +27,6 @@
 }
 
 flag {
-    name: "fix_unlocked_device_required_keys_v2"
-    namespace: "hardware_backed_security"
-    description: "Fix bugs in behavior of UnlockedDeviceRequired keystore keys"
-    bug: "296464083"
-    is_fixed_read_only: true
-}
-
-flag {
     name: "keyinfo_unlocked_device_required"
     is_exported: true
     namespace: "hardware_backed_security"
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c71f9bd..6c6add9 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8222,6 +8222,19 @@
     <permission android:name="android.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD"
                 android:protectionLevel="internal|appop" />
 
+    <!-- @hide @SystemApi
+         @FlaggedApi(android.os.Flags.FLAG_ALLOW_CONSENTLESS_BUGREPORT_DELEGATED_CONSENT)
+         Allows system applications to capture bugreport directly without consent dialog when using
+         the bugreporting API, and instead use the applications-owned consent page.
+         <p>The application still needs to hold {@link android.permission.DUMP} permission and be
+         bugreport-whitelisted to be able to capture a bugreport using the bugreporting API in the
+         first place.
+         <p>Protection level: signature|privileged
+         <p>Not for use by third-party applications. -->
+    <permission
+        android:name="android.permission.CAPTURE_CONSENTLESS_BUGREPORT_DELEGATED_CONSENT"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi Allows to call APIs that log process lifecycle events
          @hide -->
     <permission android:name="android.permission.LOG_FOREGROUND_RESOURCE_USE"
diff --git a/core/tests/coretests/src/android/content/ContentResolverTest.java b/core/tests/coretests/src/android/content/ContentResolverTest.java
index 7b70b41..c8015d4 100644
--- a/core/tests/coretests/src/android/content/ContentResolverTest.java
+++ b/core/tests/coretests/src/android/content/ContentResolverTest.java
@@ -87,7 +87,7 @@
         bitmap.compress(Bitmap.CompressFormat.PNG, 90, mImage.getOutputStream());
 
         final AssetFileDescriptor afd = new AssetFileDescriptor(
-                new ParcelFileDescriptor(mImage.getFileDescriptor()), 0, mSize, null);
+                ParcelFileDescriptor.dup(mImage.getFileDescriptor()), 0, mSize, null);
         when(mProvider.openTypedAssetFile(any(), any(), any(), any(), any())).thenReturn(
                 afd);
     }
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index c3aaf98..f8cb050 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -494,30 +494,37 @@
                         BitmapFactory.decodeByteArray(getDataBytes(), getDataOffset(),
                                 getDataLength())));
             case TYPE_URI:
-                InputStream is = getUriInputStream(context);
-                if (is != null) {
-                    final Bitmap bitmap = BitmapFactory.decodeStream(is);
-                    if (bitmap == null) {
-                        Log.w(TAG, "Unable to decode image from URI: " + getUriString());
-                        if (iconLoadDrawableReturnNullWhenUriDecodeFails()) {
-                            return null;
+                try (InputStream is = getUriInputStream(context)) {
+                    if (is != null) {
+                        final Bitmap bitmap = BitmapFactory.decodeStream(is);
+                        if (bitmap == null) {
+                            Log.w(TAG, "Unable to decode image from URI: " + getUriString());
+                            if (iconLoadDrawableReturnNullWhenUriDecodeFails()) {
+                                return null;
+                            }
                         }
+                        return new BitmapDrawable(context.getResources(), fixMaxBitmapSize(bitmap));
                     }
-                    return new BitmapDrawable(context.getResources(), fixMaxBitmapSize(bitmap));
+                } catch (IOException e) {
+                    throw new IllegalStateException(e);
                 }
                 break;
             case TYPE_URI_ADAPTIVE_BITMAP:
-                is = getUriInputStream(context);
-                if (is != null) {
-                    final Bitmap bitmap = BitmapFactory.decodeStream(is);
-                    if (bitmap == null) {
-                        Log.w(TAG, "Unable to decode image from URI: " + getUriString());
-                        if (iconLoadDrawableReturnNullWhenUriDecodeFails()) {
-                            return null;
+                try (InputStream is = getUriInputStream(context)) {
+                    if (is != null) {
+                        final Bitmap bitmap = BitmapFactory.decodeStream(is);
+                        if (bitmap == null) {
+                            Log.w(TAG, "Unable to decode image from URI: " + getUriString());
+                            if (iconLoadDrawableReturnNullWhenUriDecodeFails()) {
+                                return null;
+                            }
                         }
+                        return new AdaptiveIconDrawable(
+                                        null, new BitmapDrawable(context.getResources(),
+                                fixMaxBitmapSize(bitmap)));
                     }
-                    return new AdaptiveIconDrawable(null, new BitmapDrawable(context.getResources(),
-                            fixMaxBitmapSize(bitmap)));
+                } catch (IOException e) {
+                    throw new IllegalStateException(e);
                 }
                 break;
         }
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
new file mode 100644
index 0000000..c6dbd9b
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
@@ -0,0 +1,61 @@
+// Copyright (C) 2024 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+    default_team: "trendy_team_multitasking_windowing",
+}
+
+android_test {
+    name: "WMShellMultivalentScreenshotTestsOnDevice",
+    srcs: [
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "WindowManager-Shell",
+        "junit",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "truth",
+        "platform-parametric-runner-lib",
+        "platform-screenshot-diff-core",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+    kotlincflags: ["-Xjvm-default=all"],
+    optimize: {
+        enabled: false,
+    },
+    test_suites: ["device-tests"],
+    platform_apis: true,
+    certificate: "platform",
+    aaptflags: [
+        "--extra-packages",
+        "com.android.wm.shell",
+    ],
+    manifest: "AndroidManifest.xml",
+    asset_dirs: ["goldens/onDevice"],
+}
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifest.xml b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifest.xml
new file mode 100644
index 0000000..467dc6a
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.wm.shell.multivalentscreenshot">
+
+    <application android:debuggable="true" android:supportsRtl="true" >
+        <uses-library android:name="android.test.runner" />
+        <activity
+            android:name="platform.test.screenshot.ScreenshotActivity"
+            android:exported="true">
+        </activity>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="Multivalent screenshot tests for WindowManager-Shell"
+        android:targetPackage="com.android.wm.shell.multivalentscreenshot">
+    </instrumentation>
+
+    <!-- this permission is required by Tuner Service in screenshot tests -->
+    <uses-permission android:name="android.permission.MANAGE_USERS" />
+</manifest>
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifestRobolectric.xml b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifestRobolectric.xml
new file mode 100644
index 0000000..a7a3f13
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifestRobolectric.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.wm.shell.multivalentscreenshot">
+    <application android:debuggable="true" android:supportsRtl="true">
+        <uses-library android:name="android.test.runner" />
+        <activity
+            android:name="platform.test.screenshot.ScreenshotActivity"
+            android:exported="true">
+        </activity>
+    </application>
+</manifest>
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidTest.xml b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidTest.xml
new file mode 100644
index 0000000..75793ae
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+<configuration description="Runs Tests for WindowManagerShellLib">
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="WMShellMultivalentScreenshotTestsOnDevice.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-tag" value="WMShellMultivalentScreenshotTestsOnDevice" />
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/data/user/0/com.android.wm.shell.multivalentscreenshot/files/wmshell_screenshots" />
+        <option name="collect-on-run-ended-only" value="true" />
+    </metrics_collector>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.wm.shell.multivalentscreenshot" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png
new file mode 100644
index 0000000..eb28881
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png
new file mode 100644
index 0000000..eb28881
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties b/libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties
new file mode 100644
index 0000000..7a0527c
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties
@@ -0,0 +1,2 @@
+sdk=NEWEST_SDK
+
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/bubbles/BubbleEducationViewScreenshotTest.kt b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/bubbles/BubbleEducationViewScreenshotTest.kt
new file mode 100644
index 0000000..d35f493
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/bubbles/BubbleEducationViewScreenshotTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 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.bubbles
+
+import android.view.LayoutInflater
+import com.android.wm.shell.common.bubbles.BubblePopupView
+import com.android.wm.shell.testing.goldenpathmanager.WMShellGoldenPathManager
+import com.android.wm.shell.R
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.Displays
+import platform.test.screenshot.ViewScreenshotTestRule
+import platform.test.screenshot.getEmulatedDevicePathConfig
+
+@RunWith(ParameterizedAndroidJunit4::class)
+class BubbleEducationViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+    companion object {
+        @Parameters(name = "{0}")
+        @JvmStatic
+        fun getTestSpecs() = DeviceEmulationSpec.forDisplays(Displays.Phone, isLandscape = false)
+    }
+
+    @get:Rule
+    val screenshotRule =
+        ViewScreenshotTestRule(
+            emulationSpec,
+            WMShellGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec))
+        )
+
+    @Test
+    fun bubblesEducation() {
+        screenshotRule.screenshotTest("bubbles_education") { activity ->
+            activity.actionBar?.hide()
+            val view =
+                LayoutInflater.from(activity)
+                    .inflate(R.layout.bubble_bar_stack_education, null) as BubblePopupView
+            view.setup()
+            view
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/testing/goldenpathmanager/WMShellGoldenPathManager.kt b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/testing/goldenpathmanager/WMShellGoldenPathManager.kt
new file mode 100644
index 0000000..901b79b
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/testing/goldenpathmanager/WMShellGoldenPathManager.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 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.testing.goldenpathmanager
+
+import android.os.Build
+import androidx.test.platform.app.InstrumentationRegistry
+import platform.test.screenshot.GoldenPathManager
+import platform.test.screenshot.PathConfig
+
+/** A WM Shell specific implementation of [GoldenPathManager]. */
+class WMShellGoldenPathManager(pathConfig: PathConfig) :
+    GoldenPathManager(
+        appContext = InstrumentationRegistry.getInstrumentation().context,
+        assetsPathRelativeToBuildRoot = assetPath,
+        deviceLocalPath = deviceLocalPath,
+        pathConfig = pathConfig,
+    ) {
+
+    private companion object {
+        private const val ASSETS_PATH =
+            "frameworks/base/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice"
+        private const val ASSETS_PATH_ROBO =
+            "frameworks/base/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/" +
+                    "robolectric"
+        private val assetPath: String
+            get() = if (Build.FINGERPRINT.contains("robolectric")) ASSETS_PATH_ROBO else ASSETS_PATH
+        private val deviceLocalPath: String
+            get() =
+                InstrumentationRegistry.getInstrumentation()
+                    .targetContext
+                    .filesDir
+                    .absolutePath
+                    .toString() + "/wmshell_screenshots"
+    }
+    override fun toString(): String {
+        // This string is appended to all actual/expected screenshots on the device, so make sure
+        // it is a static value.
+        return "WMShellGoldenPathManager"
+    }
+}
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTestsForDevice b/libs/WindowManager/Shell/multivalentScreenshotTestsForDevice
new file mode 120000
index 0000000..e879efc
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTestsForDevice
@@ -0,0 +1 @@
+multivalentScreenshotTests
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index a3111b3..c9d3dbd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -324,6 +324,7 @@
         enteringHasSameLetterbox = false
         lastPostCommitFlingScale = SPRING_SCALE
         gestureProgress = 0f
+        triggerBack = false
     }
 
     protected fun applyTransform(
@@ -499,10 +500,12 @@
         }
 
         override fun onBackCancelled() {
+            triggerBack = false
             progressAnimator.onBackCancelled { finishAnimation() }
         }
 
         override fun onBackInvoked() {
+            triggerBack = true
             progressAnimator.reset()
             onGestureCommitted(progressAnimator.velocity)
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 14ae3a7..1965382 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -975,7 +975,7 @@
         if (!desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
             KtProtoLog.d(
                 WM_SHELL_DESKTOP_MODE,
-                "DesktopTasksController: switch freeform task to fullscreen oon transition" +
+                "DesktopTasksController: bring desktop tasks to front on transition" +
                     " taskId=%d",
                 task.taskId
             )
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index a1a18a9..14fa0f1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -439,14 +439,15 @@
   }
 
   @Test
-  fun showDesktopApps_dontReorderMinimizedTask() {
+  @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+  fun showDesktopApps_desktopWallpaperDisabled_dontReorderMinimizedTask() {
     val homeTask = setUpHomeTask()
     val freeformTask = setUpFreeformTask()
     val minimizedTask = setUpFreeformTask()
+
     markTaskHidden(freeformTask)
     markTaskHidden(minimizedTask)
     desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, minimizedTask.taskId)
-
     controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
 
     val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
@@ -457,6 +458,26 @@
   }
 
   @Test
+  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+  fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() {
+    setUpHomeTask()
+    val freeformTask = setUpFreeformTask()
+    val minimizedTask = setUpFreeformTask()
+
+    markTaskHidden(freeformTask)
+    markTaskHidden(minimizedTask)
+    desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, minimizedTask.taskId)
+    controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+    val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+    assertThat(wct.hierarchyOps).hasSize(2)
+    // Add desktop wallpaper activity
+    wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+    // Reorder freeform task to top, don't reorder the minimized task
+    wct.assertReorderAt(index = 1, freeformTask, toTop = true)
+  }
+
+  @Test
   fun getVisibleTaskCount_noTasks_returnsZero() {
     assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
   }
@@ -645,20 +666,37 @@
   }
 
   @Test
-  fun moveToDesktop_nonRunningTask_launchesInFreeform() {
-    whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
-
+  @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+  fun moveToDesktop_desktopWallpaperDisabled_nonRunningTask_launchesInFreeform() {
     val task = createTaskInfo(1)
-
+    whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
     whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
 
     controller.moveToDesktop(task.taskId, transitionSource = UNKNOWN)
+
     with(getLatestEnterDesktopWct()) {
       assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
     }
   }
 
   @Test
+  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+  fun moveToDesktop_desktopWallpaperEnabled_nonRunningTask_launchesInFreeform() {
+    val task = createTaskInfo(1)
+    whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
+    whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
+
+    controller.moveToDesktop(task.taskId, transitionSource = UNKNOWN)
+
+    with(getLatestEnterDesktopWct()) {
+      // Add desktop wallpaper activity
+      assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+      // Launch task
+      assertLaunchTaskAt(index = 1, task.taskId, WINDOWING_MODE_FREEFORM)
+    }
+  }
+
+  @Test
   fun moveToDesktop_topActivityTranslucent_doesNothing() {
     setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
     val task =
@@ -776,21 +814,44 @@
   }
 
   @Test
-  fun moveToDesktop_bringsTasksOverLimit_dontShowBackTask() {
+  @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+  fun moveToDesktop_desktopWallpaperDisabled_bringsTasksOver_dontShowBackTask() {
     val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
-    val homeTask = setUpHomeTask()
     val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() }
     val newTask = setUpFullscreenTask()
+    val homeTask = setUpHomeTask()
 
     controller.moveToDesktop(newTask, transitionSource = UNKNOWN)
 
     val wct = getLatestEnterDesktopWct()
     assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 1) // visible tasks + home
     wct.assertReorderAt(0, homeTask)
-    for (i in 1..<taskLimit) { // Skipping freeformTasks[0]
-      wct.assertReorderAt(index = i, task = freeformTasks[i])
-    }
-    wct.assertReorderAt(taskLimit, newTask)
+    wct.assertReorderSequenceInRange(
+      range = 1..<(taskLimit + 1),
+      *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
+      newTask
+    )
+  }
+
+  @Test
+  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+  fun moveToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() {
+    val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
+    val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() }
+    val newTask = setUpFullscreenTask()
+    setUpHomeTask()
+
+    controller.moveToDesktop(newTask, transitionSource = UNKNOWN)
+
+    val wct = getLatestEnterDesktopWct()
+    assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 1) // visible tasks + wallpaper
+    // Add desktop wallpaper activity
+    wct.assertPendingIntentAt(0, desktopWallpaperIntent)
+    wct.assertReorderSequenceInRange(
+      range = 1..<(taskLimit + 1),
+      *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
+      newTask
+    )
   }
 
   @Test
@@ -1109,41 +1170,106 @@
   }
 
   @Test
-  fun handleRequest_freeformTask_freeformNotVisible_reorderedToTop() {
+  @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+  fun handleRequest_freeformTask_desktopWallpaperDisabled_freeformNotVisible_reorderedToTop() {
     assumeTrue(ENABLE_SHELL_TRANSITIONS)
 
     val freeformTask1 = setUpFreeformTask()
-    markTaskHidden(freeformTask1)
-
     val freeformTask2 = createFreeformTask()
+
+    markTaskHidden(freeformTask1)
     val result =
         controller.handleRequest(Binder(), createTransition(freeformTask2, type = TRANSIT_TO_FRONT))
 
-    assertThat(result?.hierarchyOps?.size).isEqualTo(2)
-    result!!.assertReorderAt(1, freeformTask2, toTop = true)
+    assertNotNull(result, "Should handle request")
+    assertThat(result.hierarchyOps?.size).isEqualTo(2)
+    result.assertReorderAt(1, freeformTask2, toTop = true)
   }
 
   @Test
-  fun handleRequest_freeformTask_noOtherTasks_reorderedToTop() {
+  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+  fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_reorderedToTop() {
+    assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+    val freeformTask1 = setUpFreeformTask()
+    val freeformTask2 = createFreeformTask()
+
+    markTaskHidden(freeformTask1)
+    val result =
+      controller.handleRequest(Binder(), createTransition(freeformTask2, type = TRANSIT_TO_FRONT))
+
+    assertNotNull(result, "Should handle request")
+    assertThat(result.hierarchyOps?.size).isEqualTo(3)
+    // Add desktop wallpaper activity
+    result.assertPendingIntentAt(0, desktopWallpaperIntent)
+    // Bring active desktop tasks to front
+    result.assertReorderAt(1, freeformTask1, toTop = true)
+    // Bring new task to front
+    result.assertReorderAt(2, freeformTask2, toTop = true)
+  }
+
+  @Test
+  @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+  fun handleRequest_freeformTask_desktopWallpaperDisabled_noOtherTasks_reorderedToTop() {
     assumeTrue(ENABLE_SHELL_TRANSITIONS)
 
     val task = createFreeformTask()
     val result = controller.handleRequest(Binder(), createTransition(task))
 
-    assertThat(result?.hierarchyOps?.size).isEqualTo(1)
-    result!!.assertReorderAt(0, task, toTop = true)
+    assertNotNull(result, "Should handle request")
+    assertThat(result.hierarchyOps?.size).isEqualTo(1)
+    result.assertReorderAt(0, task, toTop = true)
   }
 
   @Test
-  fun handleRequest_freeformTask_freeformOnOtherDisplayOnly_reorderedToTop() {
+  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+  fun handleRequest_freeformTask_desktopWallpaperEnabled_noOtherTasks_reorderedToTop() {
+    assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+    val task = createFreeformTask()
+    val result = controller.handleRequest(Binder(), createTransition(task))
+
+    assertNotNull(result, "Should handle request")
+    assertThat(result.hierarchyOps?.size).isEqualTo(2)
+    // Add desktop wallpaper activity
+    result.assertPendingIntentAt(0, desktopWallpaperIntent)
+    // Bring new task to front
+    result.assertReorderAt(1, task, toTop = true)
+  }
+
+  @Test
+  @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+  fun handleRequest_freeformTask_dskWallpaperDisabled_freeformOnOtherDisplayOnly_reorderedToTop() {
     assumeTrue(ENABLE_SHELL_TRANSITIONS)
 
     val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
-    val taskSecondDisplay = createFreeformTask(displayId = SECOND_DISPLAY)
+    // Second display task
+    createFreeformTask(displayId = SECOND_DISPLAY)
 
     val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay))
-    assertThat(result?.hierarchyOps?.size).isEqualTo(1)
-    result!!.assertReorderAt(0, taskDefaultDisplay, toTop = true)
+
+    assertNotNull(result, "Should handle request")
+    assertThat(result.hierarchyOps?.size).isEqualTo(1)
+    result.assertReorderAt(0, taskDefaultDisplay, toTop = true)
+  }
+
+  @Test
+  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+  fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() {
+    assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+    val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
+    // Second display task
+    createFreeformTask(displayId = SECOND_DISPLAY)
+
+    val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay))
+
+    assertNotNull(result, "Should handle request")
+    assertThat(result.hierarchyOps?.size).isEqualTo(2)
+    // Add desktop wallpaper activity
+    result.assertPendingIntentAt(0, desktopWallpaperIntent)
+    // Bring new task to front
+    result.assertReorderAt(1, taskDefaultDisplay, toTop = true)
   }
 
   @Test
@@ -2041,6 +2167,16 @@
   }
 }
 
+/** Checks if the reorder hierarchy operations in [range] correspond to [tasks] list */
+private fun WindowContainerTransaction.assertReorderSequenceInRange(
+  range: IntRange,
+  vararg tasks: RunningTaskInfo
+) {
+  assertThat(hierarchyOps.slice(range).map { it.type to it.container })
+    .containsExactlyElementsIn(tasks.map { HIERARCHY_OP_TYPE_REORDER to it.token.asBinder() })
+    .inOrder()
+}
+
 private fun WindowContainerTransaction.assertRemoveAt(index: Int, token: WindowContainerToken) {
   assertIndexInBounds(index)
   val op = hierarchyOps[index]
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 5f84862..acfe473 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -45,13 +45,6 @@
 }
 
 flag {
-    name: "gnss_call_stop_before_set_position_mode"
-    namespace: "location"
-    description: "Flag for calling stop() before setPositionMode()"
-    bug: "306874828"
-}
-
-flag {
     name: "gnss_api_measurement_request_work_source"
     namespace: "location"
     description: "Flag for GnssMeasurementRequest WorkSource API"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 7c2f7fe..d0c4004 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -7,6 +7,7 @@
 import androidx.compose.animation.core.rememberInfiniteTransition
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
@@ -137,6 +138,9 @@
             fade(Communal.Elements.Grid)
         }
     }
+    // Disable horizontal overscroll. If the scene is overscrolled too soon after showing, this
+    // can lead to inconsistent KeyguardState changes.
+    overscroll(CommunalScenes.Communal, Orientation.Horizontal) {}
 }
 
 /**
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 7d82920..1ce51af 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -27,6 +27,7 @@
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler
 import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection
+import com.android.systemui.communal.ui.compose.section.CommunalPopupSection
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -44,81 +45,86 @@
     private val lockSection: LockSection,
     private val bottomAreaSection: BottomAreaSection,
     private val ambientStatusBarSection: AmbientStatusBarSection,
+    private val communalPopupSection: CommunalPopupSection,
 ) {
 
     @Composable
     fun SceneScope.Content(modifier: Modifier = Modifier) {
-        Layout(
-            modifier = modifier.fillMaxSize(),
-            content = {
-                Box(modifier = Modifier.fillMaxSize()) {
-                    with(ambientStatusBarSection) {
-                        AmbientStatusBar(modifier = Modifier.fillMaxWidth())
+        CommunalTouchableSurface(viewModel = viewModel, modifier = modifier) {
+            Layout(
+                modifier = Modifier.fillMaxSize(),
+                content = {
+                    Box(modifier = Modifier.fillMaxSize()) {
+                        with(communalPopupSection) { Popup() }
+                        with(ambientStatusBarSection) {
+                            AmbientStatusBar(modifier = Modifier.fillMaxWidth())
+                        }
+                        CommunalHub(
+                            viewModel = viewModel,
+                            interactionHandler = interactionHandler,
+                            dialogFactory = dialogFactory,
+                            modifier = Modifier.element(Communal.Elements.Grid)
+                        )
                     }
-                    CommunalHub(
-                        viewModel = viewModel,
-                        interactionHandler = interactionHandler,
-                        dialogFactory = dialogFactory,
-                        modifier = Modifier.element(Communal.Elements.Grid)
+                    with(lockSection) {
+                        LockIcon(
+                            overrideColor = LocalAndroidColorScheme.current.onPrimaryContainer,
+                            modifier = Modifier.element(Communal.Elements.LockIcon)
+                        )
+                    }
+                    with(bottomAreaSection) {
+                        IndicationArea(
+                            Modifier.element(Communal.Elements.IndicationArea).fillMaxWidth()
+                        )
+                    }
+                }
+            ) { measurables, constraints ->
+                val communalGridMeasurable = measurables[0]
+                val lockIconMeasurable = measurables[1]
+                val bottomAreaMeasurable = measurables[2]
+
+                val noMinConstraints =
+                    constraints.copy(
+                        minWidth = 0,
+                        minHeight = 0,
+                    )
+
+                val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+                val lockIconBounds =
+                    IntRect(
+                        left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+                        top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+                        right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+                        bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+                    )
+
+                val bottomAreaPlaceable =
+                    bottomAreaMeasurable.measure(
+                        noMinConstraints.copy(
+                            maxHeight =
+                                (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
+                        )
+                    )
+
+                val communalGridPlaceable =
+                    communalGridMeasurable.measure(
+                        noMinConstraints.copy(maxHeight = lockIconBounds.top)
+                    )
+
+                layout(constraints.maxWidth, constraints.maxHeight) {
+                    communalGridPlaceable.place(
+                        x = 0,
+                        y = 0,
+                    )
+                    lockIconPlaceable.place(
+                        x = lockIconBounds.left,
+                        y = lockIconBounds.top,
+                    )
+                    bottomAreaPlaceable.place(
+                        x = 0,
+                        y = constraints.maxHeight - bottomAreaPlaceable.height,
                     )
                 }
-                with(lockSection) {
-                    LockIcon(
-                        overrideColor = LocalAndroidColorScheme.current.onPrimaryContainer,
-                        modifier = Modifier.element(Communal.Elements.LockIcon)
-                    )
-                }
-                with(bottomAreaSection) {
-                    IndicationArea(
-                        Modifier.element(Communal.Elements.IndicationArea).fillMaxWidth()
-                    )
-                }
-            }
-        ) { measurables, constraints ->
-            val communalGridMeasurable = measurables[0]
-            val lockIconMeasurable = measurables[1]
-            val bottomAreaMeasurable = measurables[2]
-
-            val noMinConstraints =
-                constraints.copy(
-                    minWidth = 0,
-                    minHeight = 0,
-                )
-
-            val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
-            val lockIconBounds =
-                IntRect(
-                    left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
-                    top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
-                    right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
-                    bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
-                )
-
-            val bottomAreaPlaceable =
-                bottomAreaMeasurable.measure(
-                    noMinConstraints.copy(
-                        maxHeight = (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
-                    )
-                )
-
-            val communalGridPlaceable =
-                communalGridMeasurable.measure(
-                    noMinConstraints.copy(maxHeight = lockIconBounds.top)
-                )
-
-            layout(constraints.maxWidth, constraints.maxHeight) {
-                communalGridPlaceable.place(
-                    x = 0,
-                    y = 0,
-                )
-                lockIconPlaceable.place(
-                    x = lockIconBounds.left,
-                    y = lockIconBounds.top,
-                )
-                bottomAreaPlaceable.place(
-                    x = 0,
-                    y = constraints.maxHeight - bottomAreaPlaceable.height,
-                )
             }
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 9ea435e..e7f0af0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -25,7 +25,6 @@
 import android.widget.RemoteViews
 import androidx.annotation.VisibleForTesting
 import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.AnimatedVisibilityScope
 import androidx.compose.animation.core.LinearEasing
 import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.animateFloatAsState
@@ -71,7 +70,6 @@
 import androidx.compose.material.icons.filled.Check
 import androidx.compose.material.icons.filled.Close
 import androidx.compose.material.icons.outlined.Edit
-import androidx.compose.material.icons.outlined.TouchApp
 import androidx.compose.material.icons.outlined.Widgets
 import androidx.compose.material3.Button
 import androidx.compose.material3.ButtonColors
@@ -99,14 +97,16 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.geometry.CornerRadius
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.graphics.ColorMatrix
-import androidx.compose.ui.graphics.TransformOrigin
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.drawscope.Stroke
 import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.input.key.onPreviewKeyEvent
-import androidx.compose.ui.input.pointer.motionEventSpy
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.boundsInWindow
@@ -126,13 +126,11 @@
 import androidx.compose.ui.semantics.testTagsAsResourceId
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.times
 import androidx.compose.ui.viewinterop.AndroidView
-import androidx.compose.ui.window.Popup
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.window.layout.WindowMetricsCalculator
 import com.android.compose.animation.Easings.Emphasized
@@ -143,7 +141,6 @@
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth
 import com.android.systemui.communal.ui.compose.extensions.allowGestures
 import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture
 import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
@@ -151,7 +148,6 @@
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
-import com.android.systemui.communal.ui.viewmodel.PopupType
 import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.res.R
@@ -171,7 +167,6 @@
 ) {
     val communalContent by
         viewModel.communalContent.collectAsStateWithLifecycle(initialValue = emptyList())
-    val currentPopup by viewModel.currentPopup.collectAsStateWithLifecycle(initialValue = null)
     var removeButtonCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
     var toolbarSize: IntSize? by remember { mutableStateOf(null) }
     var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
@@ -213,43 +208,29 @@
                 }
                 .thenIf(!viewModel.isEditMode && !isEmptyState) {
                     Modifier.pointerInput(
-                            gridState,
-                            contentOffset,
-                            communalContent,
-                            gridCoordinates
-                        ) {
-                            detectLongPressGesture { offset ->
-                                // Deduct both grid offset relative to its container and content
-                                // offset.
-                                val adjustedOffset =
-                                    gridCoordinates?.let {
-                                        offset - it.positionInWindow() - contentOffset
-                                    }
-                                val index =
-                                    adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
-                                // Display the button only when the gesture initiates from widgets,
-                                // the CTA tile, or an empty area on the screen. UMO/smartspace have
-                                // their own long-press handlers. To prevent user confusion, we
-                                // should
-                                // not display this button.
-                                if (
-                                    index == null ||
-                                        communalContent[index].isWidgetContent() ||
-                                        communalContent[index] is
-                                            CommunalContentModel.CtaTileInViewMode
-                                ) {
-                                    viewModel.onShowCustomizeWidgetButton()
+                        gridState,
+                        contentOffset,
+                        communalContent,
+                        gridCoordinates
+                    ) {
+                        detectLongPressGesture { offset ->
+                            // Deduct both grid offset relative to its container and content
+                            // offset.
+                            val adjustedOffset =
+                                gridCoordinates?.let {
+                                    offset - it.positionInWindow() - contentOffset
                                 }
-                                val key =
-                                    index?.let { keyAtIndexIfEditable(communalContent, index) }
+                            val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
+                            val key = index?.let { keyAtIndexIfEditable(communalContent, index) }
+                            // Handle long-click on widgets and set the selected index
+                            // correctly. We only handle widgets here because long click on
+                            // empty spaces is handled by CommunalPopupSection.
+                            if (key != null) {
+                                viewModel.onLongClick()
                                 viewModel.setSelectedKey(key)
                             }
                         }
-                        .onPreviewKeyEvent {
-                            onKeyEvent(viewModel)
-                            false
-                        }
-                        .motionEventSpy { onMotionEvent(viewModel) }
+                    }
                 },
     ) {
         AccessibilityContainer(viewModel) {
@@ -342,22 +323,6 @@
                 )
             }
         }
-        if (currentPopup == PopupType.CtaTile) {
-            PopupOnDismissCtaTile(viewModel::onHidePopup)
-        }
-
-        AnimatedVisibility(
-            visible = currentPopup == PopupType.CustomizeWidgetButton,
-            modifier = Modifier.fillMaxSize()
-        ) {
-            ButtonToEditWidgets(
-                onClick = {
-                    viewModel.onHidePopup()
-                    viewModel.onOpenWidgetEditor(selectedKey.value)
-                },
-                onHide = { viewModel.onHidePopup() }
-            )
-        }
 
         if (viewModel is CommunalViewModel && dialogFactory != null) {
             val isEnableWidgetDialogShowing by
@@ -413,14 +378,6 @@
     }
 }
 
-private fun onKeyEvent(viewModel: BaseCommunalViewModel) {
-    viewModel.signalUserInteraction()
-}
-
-private fun onMotionEvent(viewModel: BaseCommunalViewModel) {
-    viewModel.signalUserInteraction()
-}
-
 @Composable
 private fun DisclaimerBottomSheetContent(onButtonClicked: () -> Unit) {
     val colors = LocalAndroidColorScheme.current
@@ -821,107 +778,6 @@
 }
 
 @Composable
-private fun AnimatedVisibilityScope.ButtonToEditWidgets(
-    onClick: () -> Unit,
-    onHide: () -> Unit,
-) {
-    Popup(
-        alignment = Alignment.TopCenter,
-        offset = IntOffset(0, 40),
-        onDismissRequest = onHide,
-    ) {
-        val colors = LocalAndroidColorScheme.current
-        Button(
-            modifier =
-                Modifier.height(56.dp)
-                    .graphicsLayer { transformOrigin = TransformOrigin(0f, 0f) }
-                    .animateEnterExit(
-                        enter =
-                            fadeIn(
-                                initialAlpha = 0f,
-                                animationSpec = tween(durationMillis = 83, easing = LinearEasing)
-                            ),
-                        exit =
-                            fadeOut(
-                                animationSpec =
-                                    tween(
-                                        durationMillis = 83,
-                                        delayMillis = 167,
-                                        easing = LinearEasing
-                                    )
-                            )
-                    )
-                    .background(colors.secondary, RoundedCornerShape(50.dp)),
-            onClick = onClick,
-        ) {
-            Row(
-                modifier =
-                    Modifier.animateEnterExit(
-                        enter =
-                            fadeIn(
-                                animationSpec =
-                                    tween(
-                                        durationMillis = 167,
-                                        delayMillis = 83,
-                                        easing = LinearEasing
-                                    )
-                            ),
-                        exit =
-                            fadeOut(
-                                animationSpec = tween(durationMillis = 167, easing = LinearEasing)
-                            )
-                    )
-            ) {
-                Icon(
-                    imageVector = Icons.Outlined.Widgets,
-                    contentDescription = stringResource(R.string.button_to_configure_widgets_text),
-                    tint = colors.onSecondary,
-                    modifier = Modifier.size(20.dp)
-                )
-                Spacer(modifier = Modifier.size(8.dp))
-                Text(
-                    text = stringResource(R.string.button_to_configure_widgets_text),
-                    style = MaterialTheme.typography.titleSmall,
-                    color = colors.onSecondary
-                )
-            }
-        }
-    }
-}
-
-@Composable
-private fun PopupOnDismissCtaTile(onHidePopup: () -> Unit) {
-    Popup(
-        alignment = Alignment.TopCenter,
-        offset = IntOffset(0, 40),
-        onDismissRequest = onHidePopup
-    ) {
-        val colors = LocalAndroidColorScheme.current
-        Row(
-            modifier =
-                Modifier.height(56.dp)
-                    .background(colors.secondary, RoundedCornerShape(50.dp))
-                    .padding(16.dp),
-            horizontalArrangement = Arrangement.Center,
-            verticalAlignment = Alignment.CenterVertically,
-        ) {
-            Icon(
-                imageVector = Icons.Outlined.TouchApp,
-                contentDescription = stringResource(R.string.popup_on_dismiss_cta_tile_text),
-                tint = colors.onSecondary,
-                modifier = Modifier.size(20.dp)
-            )
-            Spacer(modifier = Modifier.size(8.dp))
-            Text(
-                text = stringResource(R.string.popup_on_dismiss_cta_tile_text),
-                style = MaterialTheme.typography.titleSmall,
-                color = colors.onSecondary,
-            )
-        }
-    }
-}
-
-@Composable
 private fun filledButtonColors(): ButtonColors {
     val colors = LocalAndroidColorScheme.current
     return ButtonDefaults.buttonColors(
@@ -968,13 +824,26 @@
 
 /** Creates an empty card used to highlight a particular spot on the grid. */
 @Composable
-fun HighlightedItem(modifier: Modifier = Modifier) {
-    Card(
-        modifier = modifier,
-        colors = CardDefaults.cardColors(containerColor = Color.Transparent),
-        border = BorderStroke(CardOutlineWidth, LocalAndroidColorScheme.current.tertiaryFixed),
-        shape = RoundedCornerShape(16.dp)
-    ) {}
+fun HighlightedItem(modifier: Modifier = Modifier, alpha: Float = 1.0f) {
+    val brush = SolidColor(LocalAndroidColorScheme.current.primaryFixed)
+    Box(
+        modifier =
+            // drawBehind lets us draw outside the bounds of the widgets so that we don't need to
+            // resize grid items to account for the border.
+            modifier.drawBehind {
+                // 8dp of padding between the widget and the highlight on every side.
+                val padding = 8.dp.toPx()
+                drawRoundRect(
+                    brush,
+                    alpha = alpha,
+                    topLeft = Offset(-padding, -padding),
+                    size =
+                        Size(width = size.width + padding * 2, height = size.height + padding * 2),
+                    cornerRadius = CornerRadius(37.dp.toPx()),
+                    style = Stroke(width = 3.dp.toPx())
+                )
+            }
+    )
 }
 
 /** Presents a CTA tile at the end of the grid, to customize the hub. */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt
new file mode 100644
index 0000000..3707a87
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 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.communal.ui.compose
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.key.onPreviewKeyEvent
+import androidx.compose.ui.input.pointer.motionEventSpy
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+
+@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
+@Composable
+fun CommunalTouchableSurface(
+    viewModel: CommunalViewModel,
+    modifier: Modifier = Modifier,
+    content: @Composable BoxScope.() -> Unit,
+) {
+
+    val interactionSource = remember { MutableInteractionSource() }
+
+    Box(
+        modifier =
+            modifier
+                .combinedClickable(
+                    onLongClick = viewModel::onLongClick,
+                    onClick = viewModel::onClick,
+                    interactionSource = interactionSource,
+                    indication = null,
+                )
+                .onPreviewKeyEvent {
+                    viewModel.signalUserInteraction()
+                    false
+                }
+                .motionEventSpy { viewModel.signalUserInteraction() }
+    ) {
+        content()
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index 33d2cc4..1051095 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -16,10 +16,9 @@
 
 package com.android.systemui.communal.ui.compose
 
-import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
+import androidx.compose.animation.core.spring
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
 import androidx.compose.foundation.gestures.scrollBy
@@ -34,13 +33,10 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.input.pointer.pointerInteropFilter
-import androidx.compose.ui.semantics.clearAndSetSemantics
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.toOffset
 import androidx.compose.ui.unit.toSize
@@ -107,8 +103,7 @@
         get() =
             draggingItemLayoutInfo?.let { item ->
                 draggingItemInitialOffset + draggingItemDraggedDelta - item.offset.toOffset()
-            }
-                ?: Offset.Zero
+            } ?: Offset.Zero
 
     private val draggingItemLayoutInfo: LazyGridItemInfo?
         get() = state.layoutInfo.visibleItemsInfo.firstOrNull { it.index == draggingItemIndex }
@@ -238,7 +233,6 @@
 }
 
 /** Wrap LazyGrid item with additional modifier needed for drag and drop. */
-@OptIn(ExperimentalComposeUiApi::class)
 @ExperimentalFoundationApi
 @Composable
 fun LazyGridItemScope.DraggableItem(
@@ -270,22 +264,18 @@
             Modifier.animateItemPlacement()
         }
 
+    // Animate the highlight alpha manually as alpha modifier (and AnimatedVisibility) clips the
+    // widget to bounds, which cuts off the highlight as we are drawing outside the widget bounds.
+    val alpha by
+        animateFloatAsState(
+            targetValue =
+                if ((dragging || selected) && !dragDropState.isDraggingToRemove) 1f else 0f,
+            animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
+            label = "Widget outline alpha"
+        )
+
     Box(modifier) {
+        HighlightedItem(Modifier.matchParentSize(), alpha = alpha)
         Box(draggingModifier) { content(dragging) }
-        AnimatedVisibility(
-            modifier =
-                Modifier.matchParentSize()
-                    // Avoid taking focus away from the content when using explore-by-touch with
-                    // accessibility tools.
-                    .clearAndSetSemantics {}
-                    // Do not consume motion events in the highlighted item and pass them down to
-                    // the content.
-                    .pointerInteropFilter { false },
-            visible = (dragging || selected) && !dragDropState.isDraggingToRemove,
-            enter = fadeIn(),
-            exit = fadeOut()
-        ) {
-            HighlightedItem(Modifier.matchParentSize())
-        }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
new file mode 100644
index 0000000..1ea73e1
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2024 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.communal.ui.compose.section
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.AnimatedVisibilityScope
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.TouchApp
+import androidx.compose.material.icons.outlined.Widgets
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.TransformOrigin
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Popup
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.ui.viewmodel.PopupType
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+class CommunalPopupSection
+@Inject
+constructor(
+    private val viewModel: CommunalViewModel,
+) {
+
+    @Composable
+    fun Popup() {
+        val currentPopup by viewModel.currentPopup.collectAsStateWithLifecycle(initialValue = null)
+
+        if (currentPopup == PopupType.CtaTile) {
+            PopupOnDismissCtaTile(viewModel::onHidePopup)
+        }
+
+        AnimatedVisibility(
+            visible = currentPopup == PopupType.CustomizeWidgetButton,
+            modifier = Modifier.fillMaxSize()
+        ) {
+            ButtonToEditWidgets(
+                onClick = {
+                    viewModel.onHidePopup()
+                    viewModel.onOpenWidgetEditor()
+                },
+                onDismissRequest = {
+                    viewModel.onHidePopup()
+                    viewModel.setSelectedKey(null)
+                }
+            )
+        }
+    }
+
+    @Composable
+    private fun AnimatedVisibilityScope.ButtonToEditWidgets(
+        onClick: () -> Unit,
+        onDismissRequest: () -> Unit,
+    ) {
+        Popup(
+            alignment = Alignment.TopCenter,
+            offset = IntOffset(0, 40),
+            onDismissRequest = onDismissRequest,
+        ) {
+            val colors = LocalAndroidColorScheme.current
+            Button(
+                modifier =
+                    Modifier.height(56.dp)
+                        .graphicsLayer { transformOrigin = TransformOrigin(0f, 0f) }
+                        .animateEnterExit(
+                            enter =
+                                fadeIn(
+                                    initialAlpha = 0f,
+                                    animationSpec =
+                                        tween(durationMillis = 83, easing = LinearEasing)
+                                ),
+                            exit =
+                                fadeOut(
+                                    animationSpec =
+                                        tween(
+                                            durationMillis = 83,
+                                            delayMillis = 167,
+                                            easing = LinearEasing
+                                        )
+                                )
+                        )
+                        .background(colors.secondary, RoundedCornerShape(50.dp)),
+                onClick = onClick,
+            ) {
+                Row(
+                    modifier =
+                        Modifier.animateEnterExit(
+                            enter =
+                                fadeIn(
+                                    animationSpec =
+                                        tween(
+                                            durationMillis = 167,
+                                            delayMillis = 83,
+                                            easing = LinearEasing
+                                        )
+                                ),
+                            exit =
+                                fadeOut(
+                                    animationSpec =
+                                        tween(durationMillis = 167, easing = LinearEasing)
+                                )
+                        )
+                ) {
+                    Icon(
+                        imageVector = Icons.Outlined.Widgets,
+                        contentDescription =
+                            stringResource(R.string.button_to_configure_widgets_text),
+                        tint = colors.onSecondary,
+                        modifier = Modifier.size(20.dp)
+                    )
+                    Spacer(modifier = Modifier.size(8.dp))
+                    Text(
+                        text = stringResource(R.string.button_to_configure_widgets_text),
+                        style = MaterialTheme.typography.titleSmall,
+                        color = colors.onSecondary
+                    )
+                }
+            }
+        }
+    }
+
+    @Composable
+    private fun PopupOnDismissCtaTile(onDismissRequest: () -> Unit) {
+        Popup(
+            alignment = Alignment.TopCenter,
+            offset = IntOffset(0, 40),
+            onDismissRequest = onDismissRequest
+        ) {
+            val colors = LocalAndroidColorScheme.current
+            Row(
+                modifier =
+                    Modifier.height(56.dp)
+                        .background(colors.secondary, RoundedCornerShape(50.dp))
+                        .padding(16.dp),
+                horizontalArrangement = Arrangement.Center,
+                verticalAlignment = Alignment.CenterVertically,
+            ) {
+                Icon(
+                    imageVector = Icons.Outlined.TouchApp,
+                    contentDescription = stringResource(R.string.popup_on_dismiss_cta_tile_text),
+                    tint = colors.onSecondary,
+                    modifier = Modifier.size(20.dp)
+                )
+                Spacer(modifier = Modifier.size(8.dp))
+                Text(
+                    text = stringResource(R.string.popup_on_dismiss_cta_tile_text),
+                    style = MaterialTheme.typography.titleSmall,
+                    color = colors.onSecondary,
+                )
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 7a5f81c..51991de 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -75,6 +75,7 @@
 import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
 import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
+import com.android.systemui.statusbar.KeyguardIndicationController
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.repository.fakeUserRepository
@@ -91,6 +92,7 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
 import platform.test.runner.parameterized.Parameters
@@ -154,13 +156,14 @@
                 context.resources,
                 kosmos.keyguardTransitionInteractor,
                 kosmos.keyguardInteractor,
+                mock<KeyguardIndicationController>(),
                 kosmos.communalSceneInteractor,
                 kosmos.communalInteractor,
                 kosmos.communalSettingsInteractor,
                 kosmos.communalTutorialInteractor,
                 kosmos.shadeInteractor,
                 mediaHost,
-                logcatLogBuffer("CommunalViewModelTest"),
+                logcatLogBuffer("CommunalViewModelTest")
             )
     }
 
@@ -358,7 +361,7 @@
             val currentPopup by collectLastValue(underTest.currentPopup)
 
             assertThat(currentPopup).isNull()
-            underTest.onShowCustomizeWidgetButton()
+            underTest.onLongClick()
             assertThat(currentPopup).isEqualTo(PopupType.CustomizeWidgetButton)
             advanceTimeBy(POPUP_AUTO_HIDE_TIMEOUT_MS)
             assertThat(currentPopup).isNull()
@@ -370,7 +373,7 @@
             tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
             val currentPopup by collectLastValue(underTest.currentPopup)
 
-            underTest.onShowCustomizeWidgetButton()
+            underTest.onLongClick()
             assertThat(currentPopup).isEqualTo(PopupType.CustomizeWidgetButton)
 
             underTest.onHidePopup()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
index cb4e2d3..16c7090 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
@@ -28,12 +28,14 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.fakeShadeRepository
 import com.android.systemui.shade.ui.viewmodel.notificationsShadeSceneViewModel
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -56,7 +58,7 @@
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
     private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
 
-    private val underTest = kosmos.notificationsShadeSceneViewModel
+    private val underTest by lazy { kosmos.notificationsShadeSceneViewModel }
 
     @Test
     fun upTransitionSceneKey_deviceLocked_lockscreen() =
@@ -65,11 +67,23 @@
             lockDevice()
 
             assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(destinationScenes?.get(Swipe.Down)).isNull()
             assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value)
                 .isEqualTo(Scenes.Lockscreen)
         }
 
     @Test
+    fun upTransitionSceneKey_deviceLocked_keyguardDisabled_gone() =
+        testScope.runTest {
+            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            lockDevice()
+            kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+
+            assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
     fun upTransitionSceneKey_deviceUnlocked_gone() =
         testScope.runTest {
             val destinationScenes by collectLastValue(underTest.destinationScenes)
@@ -77,6 +91,33 @@
             unlockDevice()
 
             assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(destinationScenes?.get(Swipe.Down)).isNull()
+            assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
+    fun downTransitionSceneKey_deviceLocked_bottomAligned_lockscreen() =
+        testScope.runTest {
+            kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
+            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            lockDevice()
+
+            assertThat(destinationScenes?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(destinationScenes?.get(Swipe.Up)).isNull()
+            assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value)
+                .isEqualTo(Scenes.Lockscreen)
+        }
+
+    @Test
+    fun downTransitionSceneKey_deviceUnlocked_bottomAligned_gone() =
+        testScope.runTest {
+            kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
+            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            lockDevice()
+            unlockDevice()
+
+            assertThat(destinationScenes?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(destinationScenes?.get(Swipe.Up)).isNull()
             assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index d43d50a..e01ffa6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.controls.data.repository.mediaFilterRepository
@@ -160,6 +161,37 @@
         }
 
     @Test
+    fun destinations_whenNotCustomizing_withPreviousSceneLockscreen_butLockscreenDisabled() =
+        testScope.runTest {
+            overrideResource(R.bool.config_use_split_notification_shade, false)
+            qsFlexiglassAdapter.setCustomizing(false)
+            val destinations by collectLastValue(underTest.destinationScenes)
+
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val backScene by collectLastValue(sceneBackInteractor.backScene)
+            val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+            sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+            sceneInteractor.changeScene(Scenes.QuickSettings, "reason")
+
+            kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
+            assertThat(backScene).isNull()
+            assertThat(destinations)
+                .isEqualTo(
+                    mapOf(
+                        Back to UserActionResult(Scenes.Shade),
+                        Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
+                        Swipe(
+                            fromSource = Edge.Bottom,
+                            direction = SwipeDirection.Up,
+                        ) to UserActionResult(SceneFamilies.Home)
+                    )
+                )
+            assertThat(homeScene).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
     fun destinations_whenNotCustomizing_authMethodSwipe_lockscreenNotDismissed() =
         testScope.runTest {
             overrideResource(R.bool.config_use_split_notification_shade, false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
index ac67ac8..411a7a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
@@ -28,12 +28,14 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.fakeShadeRepository
 import com.android.systemui.shade.ui.viewmodel.quickSettingsShadeSceneViewModel
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -56,7 +58,7 @@
     private val sceneInteractor = kosmos.sceneInteractor
     private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
 
-    private val underTest = kosmos.quickSettingsShadeSceneViewModel
+    private val underTest by lazy { kosmos.quickSettingsShadeSceneViewModel }
 
     @Test
     fun upTransitionSceneKey_deviceLocked_lockscreen() =
@@ -66,10 +68,23 @@
             lockDevice()
 
             assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(destinationScenes?.get(Swipe.Down)).isNull()
             assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
         }
 
     @Test
+    fun upTransitionSceneKey_deviceLocked_keyguardDisabled_gone() =
+        testScope.runTest {
+            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+            lockDevice()
+            kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+
+            assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(homeScene).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
     fun upTransitionSceneKey_deviceUnlocked_gone() =
         testScope.runTest {
             val destinationScenes by collectLastValue(underTest.destinationScenes)
@@ -78,6 +93,34 @@
             unlockDevice()
 
             assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(destinationScenes?.get(Swipe.Down)).isNull()
+            assertThat(homeScene).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
+    fun downTransitionSceneKey_deviceLocked_bottomAligned_lockscreen() =
+        testScope.runTest {
+            kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
+            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+            lockDevice()
+
+            assertThat(destinationScenes?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(destinationScenes?.get(Swipe.Up)).isNull()
+            assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
+        }
+
+    @Test
+    fun downTransitionSceneKey_deviceUnlocked_bottomAligned_gone() =
+        testScope.runTest {
+            kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
+            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+            lockDevice()
+            unlockDevice()
+
+            assertThat(destinationScenes?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(destinationScenes?.get(Swipe.Up)).isNull()
             assertThat(homeScene).isEqualTo(Scenes.Gone)
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 9edc3af..ae615dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -36,12 +36,15 @@
 import com.android.systemui.classifier.falsingManager
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeTrustRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.model.sysUiState
@@ -1339,6 +1342,111 @@
             assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
         }
 
+    @Test
+    fun switchToGone_whenKeyguardBecomesDisabled() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            prepareState()
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            underTest.start()
+
+            kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+            runCurrent()
+
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
+    fun switchToGone_whenKeyguardBecomesDisabled_whenOnShadeScene() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            prepareState(
+                initialSceneKey = Scenes.Shade,
+            )
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
+            underTest.start()
+
+            kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+            runCurrent()
+
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
+    fun doesNotSwitchToGone_whenKeyguardBecomesDisabled_whenInLockdownMode() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            prepareState()
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            underTest.start()
+
+            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+            kosmos.fakeBiometricSettingsRepository.setIsUserInLockdown(true)
+            kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+            runCurrent()
+
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+        }
+
+    @Test
+    fun doesNotSwitchToGone_whenKeyguardBecomesDisabled_whenDeviceEntered() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            prepareState(
+                isDeviceUnlocked = true,
+                initialSceneKey = Scenes.Gone,
+            )
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
+            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isTrue()
+            underTest.start()
+            sceneInteractor.changeScene(Scenes.Shade, "")
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
+            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isTrue()
+
+            kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+            runCurrent()
+
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
+        }
+
+    @Test
+    fun switchToLockscreen_whenKeyguardBecomesEnabled_afterHidingWhenDisabled() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            prepareState()
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            underTest.start()
+            kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+            runCurrent()
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
+
+            kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(true)
+            runCurrent()
+
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+        }
+
+    @Test
+    fun doesNotSwitchToLockscreen_whenKeyguardBecomesEnabled_ifAuthMethodBecameInsecure() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            prepareState()
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            underTest.start()
+            kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+            runCurrent()
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
+            runCurrent()
+
+            kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(true)
+            runCurrent()
+
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
+        }
+
     private fun TestScope.emulateSceneTransition(
         transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
         toScene: SceneKey,
@@ -1381,15 +1489,10 @@
         isDeviceProvisioned: Boolean = true,
         isInteractive: Boolean = true,
     ): MutableStateFlow<ObservableTransitionState> {
-        if (authenticationMethod?.isSecure == true) {
-            assert(isLockscreenEnabled) {
-                "Lockscreen cannot be disabled while having a secure authentication method"
-            }
-            if (isDeviceUnlocked) {
-                kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
-                    SuccessFingerprintAuthenticationStatus(0, true)
-                )
-            }
+        if (isDeviceUnlocked) {
+            kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
         }
 
         check(initialSceneKey != Scenes.Gone || isDeviceUnlocked) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index c53cdf8..a0295c9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.controls.data.repository.mediaFilterRepository
@@ -139,6 +140,21 @@
         }
 
     @Test
+    fun upTransitionSceneKey_keyguardDisabled_gone() =
+        testScope.runTest {
+            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+
+            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
+                .isEqualTo(SceneFamilies.Home)
+            assertThat(homeScene).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
     fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
         testScope.runTest {
             val destinationScenes by collectLastValue(underTest.destinationScenes)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
index 004d5db..c2c5277 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
@@ -42,4 +42,7 @@
     fun accessibilityQsShortcutsRepository(
         impl: AccessibilityQsShortcutsRepositoryImpl
     ): AccessibilityQsShortcutsRepository
+
+    @Binds
+    fun magnification(impl: MagnificationImpl): Magnification
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
index 3c4c003..3c0ac9a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
@@ -56,7 +56,7 @@
 import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
-class FullscreenMagnificationController implements ComponentCallbacks {
+public class FullscreenMagnificationController implements ComponentCallbacks {
 
     private static final String TAG = "FullscreenMagnificationController";
     private final Context mContext;
@@ -87,7 +87,7 @@
     };
     private final long mLongAnimationTimeMs;
 
-    FullscreenMagnificationController(
+    public FullscreenMagnificationController(
             @UiContext Context context,
             @Main Handler handler,
             @Main Executor executor,
@@ -157,7 +157,7 @@
      * there is an activation change.
      */
     @UiThread
-    void onFullscreenMagnificationActivationChanged(boolean activated) {
+    public void onFullscreenMagnificationActivationChanged(boolean activated) {
         final boolean changed = (mFullscreenMagnificationActivated != activated);
         if (changed) {
             mFullscreenMagnificationActivated = activated;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index e22a4e4..48e5f0b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -16,618 +16,54 @@
 
 package com.android.systemui.accessibility;
 
-import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
-import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
-import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
-
-import static com.android.systemui.accessibility.AccessibilityLogger.MagnificationSettingsEvent;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
-
 import android.annotation.MainThread;
 import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.Rect;
-import android.hardware.display.DisplayManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.SparseArray;
-import android.view.Display;
-import android.view.IWindowManager;
-import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.IMagnificationConnection;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
-import android.window.InputTransferToken;
 
-import androidx.annotation.NonNull;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.CoreStartable;
-import com.android.systemui.Flags;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.settings.DisplayTracker;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.util.settings.SecureSettings;
 
-import java.io.PrintWriter;
-import java.util.concurrent.Executor;
-import java.util.function.Supplier;
-
-import javax.inject.Inject;
-
-/**
- * Class to handle the interaction with
- * {@link com.android.server.accessibility.AccessibilityManagerService}. It invokes
- * {@link AccessibilityManager#setMagnificationConnection(IMagnificationConnection)}
- * when {@code IStatusBar#requestWindowMagnificationConnection(boolean)} is called.
- */
-@SysUISingleton
-public class Magnification implements CoreStartable, CommandQueue.Callbacks {
-    private static final String TAG = "Magnification";
-
-    @VisibleForTesting static final int DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS = 300;
-    private static final int MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL = 1;
-
-    private final ModeSwitchesController mModeSwitchesController;
-    private final Context mContext;
-    private final Handler mHandler;
-    private final Executor mExecutor;
-    private final AccessibilityManager mAccessibilityManager;
-    private final CommandQueue mCommandQueue;
-    private final OverviewProxyService mOverviewProxyService;
-    private final DisplayTracker mDisplayTracker;
-    private final AccessibilityLogger mA11yLogger;
-
-    private MagnificationConnectionImpl mMagnificationConnectionImpl;
-    private SysUiState mSysUiState;
-
-    @VisibleForTesting
-    SparseArray<SparseArray<Float>> mUsersScales = new SparseArray();
-
-    private static class WindowMagnificationControllerSupplier extends
-            DisplayIdIndexSupplier<WindowMagnificationController> {
-
-        private final Context mContext;
-        private final Handler mHandler;
-        private final WindowMagnifierCallback mWindowMagnifierCallback;
-        private final SysUiState mSysUiState;
-        private final SecureSettings mSecureSettings;
-
-        WindowMagnificationControllerSupplier(Context context, Handler handler,
-                WindowMagnifierCallback windowMagnifierCallback,
-                DisplayManager displayManager, SysUiState sysUiState,
-                SecureSettings secureSettings) {
-            super(displayManager);
-            mContext = context;
-            mHandler = handler;
-            mWindowMagnifierCallback = windowMagnifierCallback;
-            mSysUiState = sysUiState;
-            mSecureSettings = secureSettings;
-        }
-
-        @Override
-        protected WindowMagnificationController createInstance(Display display) {
-            final Context windowContext = mContext.createWindowContext(display,
-                    Flags.createWindowlessWindowMagnifier()
-                            ? TYPE_ACCESSIBILITY_OVERLAY
-                            : TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
-                    /* options */ null);
-            windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
-
-            Supplier<SurfaceControlViewHost> scvhSupplier = () ->
-                    Flags.createWindowlessWindowMagnifier() ? new SurfaceControlViewHost(mContext,
-                            mContext.getDisplay(), new InputTransferToken(), TAG) : null;
-
-            return new WindowMagnificationController(
-                    windowContext,
-                    mHandler,
-                    new WindowMagnificationAnimationController(windowContext),
-                    /* mirrorWindowControl= */ null,
-                    new SurfaceControl.Transaction(),
-                    mWindowMagnifierCallback,
-                    mSysUiState,
-                    mSecureSettings,
-                    scvhSupplier,
-                    new SfVsyncFrameCallbackProvider(),
-                    WindowManagerGlobal::getWindowSession);
-        }
-    }
-
-    @VisibleForTesting
-    DisplayIdIndexSupplier<WindowMagnificationController> mWindowMagnificationControllerSupplier;
-
-    private static class FullscreenMagnificationControllerSupplier extends
-            DisplayIdIndexSupplier<FullscreenMagnificationController> {
-
-        private final Context mContext;
-        private final Handler mHandler;
-        private final Executor mExecutor;
-        private final IWindowManager mIWindowManager;
-
-        FullscreenMagnificationControllerSupplier(Context context,
-                DisplayManager displayManager,
-                Handler handler,
-                Executor executor, IWindowManager iWindowManager) {
-            super(displayManager);
-            mContext = context;
-            mHandler = handler;
-            mExecutor = executor;
-            mIWindowManager = iWindowManager;
-        }
-
-        @Override
-        protected FullscreenMagnificationController createInstance(Display display) {
-            final Context windowContext = mContext.createWindowContext(display,
-                    TYPE_ACCESSIBILITY_OVERLAY, /* options */ null);
-            Supplier<SurfaceControlViewHost> scvhSupplier = () -> new SurfaceControlViewHost(
-                    mContext, mContext.getDisplay(), new InputTransferToken(), TAG);
-            windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
-            return new FullscreenMagnificationController(
-                    windowContext,
-                    mHandler,
-                    mExecutor,
-                    windowContext.getSystemService(AccessibilityManager.class),
-                    windowContext.getSystemService(WindowManager.class),
-                    mIWindowManager,
-                    scvhSupplier);
-        }
-    }
-
-    @VisibleForTesting
-    DisplayIdIndexSupplier<FullscreenMagnificationController>
-            mFullscreenMagnificationControllerSupplier;
-
-    private static class SettingsSupplier extends
-            DisplayIdIndexSupplier<MagnificationSettingsController> {
-
-        private final Context mContext;
-        private final MagnificationSettingsController.Callback mSettingsControllerCallback;
-        private final SecureSettings mSecureSettings;
-
-        SettingsSupplier(Context context,
-                MagnificationSettingsController.Callback settingsControllerCallback,
-                DisplayManager displayManager,
-                SecureSettings secureSettings) {
-            super(displayManager);
-            mContext = context;
-            mSettingsControllerCallback = settingsControllerCallback;
-            mSecureSettings = secureSettings;
-        }
-
-        @Override
-        protected MagnificationSettingsController createInstance(Display display) {
-            final Context windowContext = mContext.createWindowContext(display,
-                    TYPE_ACCESSIBILITY_OVERLAY, /* options */ null);
-            windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
-            return new MagnificationSettingsController(
-                    windowContext,
-                    new SfVsyncFrameCallbackProvider(),
-                    mSettingsControllerCallback,
-                    mSecureSettings);
-        }
-    }
-
-    @VisibleForTesting
-    DisplayIdIndexSupplier<MagnificationSettingsController> mMagnificationSettingsSupplier;
-
-    @Inject
-    public Magnification(Context context,
-            @Main Handler mainHandler, @Main Executor executor,
-            CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
-            SysUiState sysUiState, OverviewProxyService overviewProxyService,
-            SecureSettings secureSettings, DisplayTracker displayTracker,
-            DisplayManager displayManager, AccessibilityLogger a11yLogger,
-            IWindowManager iWindowManager) {
-        this(context, mainHandler.getLooper(), executor, commandQueue,
-                modeSwitchesController, sysUiState, overviewProxyService, secureSettings,
-                displayTracker, displayManager, a11yLogger, iWindowManager);
-    }
-
-    @VisibleForTesting
-    public Magnification(Context context, Looper looper, @Main Executor executor,
-            CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
-            SysUiState sysUiState, OverviewProxyService overviewProxyService,
-            SecureSettings secureSettings, DisplayTracker displayTracker,
-            DisplayManager displayManager, AccessibilityLogger a11yLogger,
-            IWindowManager iWindowManager) {
-        mContext = context;
-        mHandler = new Handler(looper) {
-            @Override
-            public void handleMessage(@NonNull Message msg) {
-                if (msg.what == MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL) {
-                    showMagnificationButtonInternal(msg.arg1, msg.arg2);
-                }
-            }
-        };
-        mExecutor = executor;
-        mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
-        mCommandQueue = commandQueue;
-        mModeSwitchesController = modeSwitchesController;
-        mSysUiState = sysUiState;
-        mOverviewProxyService = overviewProxyService;
-        mDisplayTracker = displayTracker;
-        mA11yLogger = a11yLogger;
-        mWindowMagnificationControllerSupplier = new WindowMagnificationControllerSupplier(context,
-                mHandler, mWindowMagnifierCallback,
-                displayManager, sysUiState, secureSettings);
-        mFullscreenMagnificationControllerSupplier = new FullscreenMagnificationControllerSupplier(
-                context, displayManager, mHandler, mExecutor, iWindowManager);
-        mMagnificationSettingsSupplier = new SettingsSupplier(context,
-                mMagnificationSettingsControllerCallback, displayManager, secureSettings);
-
-        mModeSwitchesController.setClickListenerDelegate(
-                displayId -> mHandler.post(() -> {
-                    toggleSettingsPanelVisibility(displayId);
-                }));
-    }
-
-    @Override
-    public void start() {
-        mCommandQueue.addCallback(this);
-        mOverviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() {
-            @Override
-            public void onConnectionChanged(boolean isConnected) {
-                if (isConnected) {
-                    updateSysUiStateFlag();
-                }
-            }
-        });
-    }
-
-    private void updateSysUiStateFlag() {
-        //TODO(b/187510533): support multi-display once SysuiState supports it.
-        final WindowMagnificationController controller =
-                mWindowMagnificationControllerSupplier.valueAt(
-                        mDisplayTracker.getDefaultDisplayId());
-        if (controller != null) {
-            controller.updateSysUIStateFlag();
-        } else {
-            // The instance is initialized when there is an IPC request. Considering
-            // self-crash cases, we need to reset the flag in such situation.
-            mSysUiState.setFlag(SYSUI_STATE_MAGNIFICATION_OVERLAP, false)
-                    .commitUpdate(mDisplayTracker.getDefaultDisplayId());
-        }
-    }
+/** Interface for managing magnification connection calls from system process. */
+public interface Magnification extends CoreStartable {
 
     @MainThread
-    void enableWindowMagnification(int displayId, float scale, float centerX, float centerY,
-            float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY,
-            @Nullable IRemoteMagnificationAnimationCallback callback) {
-        final WindowMagnificationController windowMagnificationController =
-                mWindowMagnificationControllerSupplier.get(displayId);
-        if (windowMagnificationController != null) {
-            windowMagnificationController.enableWindowMagnification(scale, centerX, centerY,
-                    magnificationFrameOffsetRatioX, magnificationFrameOffsetRatioY, callback);
-        }
-    }
+    void enableWindowMagnification(
+            int displayId,
+            float scale,
+            float centerX,
+            float centerY,
+            float magnificationFrameOffsetRatioX,
+            float magnificationFrameOffsetRatioY,
+            @Nullable IRemoteMagnificationAnimationCallback callback);
 
     @MainThread
-    void setScaleForWindowMagnification(int displayId, float scale) {
-        final WindowMagnificationController windowMagnificationController =
-                mWindowMagnificationControllerSupplier.get(displayId);
-        if (windowMagnificationController != null) {
-            windowMagnificationController.setScale(scale);
-        }
-    }
+    void setScaleForWindowMagnification(int displayId, float scale);
 
     @MainThread
-    void moveWindowMagnifier(int displayId, float offsetX, float offsetY) {
-        final WindowMagnificationController windowMagnificationcontroller =
-                mWindowMagnificationControllerSupplier.get(displayId);
-        if (windowMagnificationcontroller != null) {
-            windowMagnificationcontroller.moveWindowMagnifier(offsetX, offsetY);
-        }
-    }
+    void moveWindowMagnifier(int displayId, float offsetX, float offsetY);
 
     @MainThread
-    void moveWindowMagnifierToPositionInternal(int displayId, float positionX, float positionY,
-            IRemoteMagnificationAnimationCallback callback) {
-        final WindowMagnificationController windowMagnificationController =
-                mWindowMagnificationControllerSupplier.get(displayId);
-        if (windowMagnificationController != null) {
-            windowMagnificationController.moveWindowMagnifierToPosition(positionX, positionY,
-                    callback);
-        }
-    }
+    void disableWindowMagnification(
+            int displayId, @Nullable IRemoteMagnificationAnimationCallback callback);
 
     @MainThread
-    void disableWindowMagnification(int displayId,
-            @Nullable IRemoteMagnificationAnimationCallback callback) {
-        final WindowMagnificationController windowMagnificationController =
-                mWindowMagnificationControllerSupplier.get(displayId);
-        if (windowMagnificationController != null) {
-            windowMagnificationController.deleteWindowMagnification(callback);
-        }
-    }
+    void onFullscreenMagnificationActivationChanged(int displayId, boolean activated);
 
     @MainThread
-    void onFullscreenMagnificationActivationChanged(int displayId, boolean activated) {
-        final FullscreenMagnificationController fullscreenMagnificationController =
-                mFullscreenMagnificationControllerSupplier.get(displayId);
-        if (fullscreenMagnificationController != null) {
-            fullscreenMagnificationController.onFullscreenMagnificationActivationChanged(activated);
-        }
-    }
+    void showMagnificationButton(int displayId, int magnificationMode);
 
     @MainThread
-    void toggleSettingsPanelVisibility(int displayId) {
-        final MagnificationSettingsController magnificationSettingsController =
-                mMagnificationSettingsSupplier.get(displayId);
-        if (magnificationSettingsController != null) {
-            magnificationSettingsController.toggleSettingsPanelVisibility();
-        }
-    }
+    void removeMagnificationButton(int displayId);
 
     @MainThread
-    void hideMagnificationSettingsPanel(int displayId) {
-        final MagnificationSettingsController magnificationSettingsController =
-                mMagnificationSettingsSupplier.get(displayId);
-        if (magnificationSettingsController != null) {
-            magnificationSettingsController.closeMagnificationSettings();
-        }
-    }
-
-    boolean isMagnificationSettingsPanelShowing(int displayId) {
-        final MagnificationSettingsController magnificationSettingsController =
-                mMagnificationSettingsSupplier.get(displayId);
-        if (magnificationSettingsController != null) {
-            return magnificationSettingsController.isMagnificationSettingsShowing();
-        }
-        return false;
-    }
+    void setUserMagnificationScale(int userId, int displayId, float scale);
 
     @MainThread
-    void showMagnificationButton(int displayId, int magnificationMode) {
-        if (Flags.delayShowMagnificationButton()) {
-            if (mHandler.hasMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL)) {
-                return;
-            }
-            mHandler.sendMessageDelayed(
-                    mHandler.obtainMessage(
-                            MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL, displayId, magnificationMode),
-                    DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS);
-        } else {
-            showMagnificationButtonInternal(displayId, magnificationMode);
-        }
-    }
+    void hideMagnificationSettingsPanel(int displayId);
 
     @MainThread
-    private void showMagnificationButtonInternal(int displayId, int magnificationMode) {
-        // not to show mode switch button if settings panel is already showing to
-        // prevent settings panel be covered by the button.
-        if (isMagnificationSettingsPanelShowing(displayId)) {
-            return;
-        }
-        mModeSwitchesController.showButton(displayId, magnificationMode);
-    }
-
-    @MainThread
-    void removeMagnificationButton(int displayId) {
-        if (Flags.delayShowMagnificationButton()) {
-            mHandler.removeMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL);
-        }
-        mModeSwitchesController.removeButton(displayId);
-    }
-
-    @MainThread
-    void setUserMagnificationScale(int userId, int displayId, float scale) {
-        SparseArray<Float> scales = mUsersScales.get(userId);
-        if (scales == null) {
-            scales = new SparseArray<>();
-            mUsersScales.put(userId, scales);
-        }
-        if (scales.contains(displayId) && scales.get(displayId) == scale) {
-            return;
-        }
-        scales.put(displayId, scale);
-
-        final MagnificationSettingsController magnificationSettingsController =
-                mMagnificationSettingsSupplier.get(displayId);
-        magnificationSettingsController.setMagnificationScale(scale);
-    }
-
-    @VisibleForTesting
-    final WindowMagnifierCallback mWindowMagnifierCallback = new WindowMagnifierCallback() {
-        @Override
-        public void onWindowMagnifierBoundsChanged(int displayId, Rect frame) {
-            if (mMagnificationConnectionImpl != null) {
-                mMagnificationConnectionImpl.onWindowMagnifierBoundsChanged(displayId, frame);
-            }
-        }
-
-        @Override
-        public void onSourceBoundsChanged(int displayId, Rect sourceBounds) {
-            if (mMagnificationConnectionImpl != null) {
-                mMagnificationConnectionImpl.onSourceBoundsChanged(displayId, sourceBounds);
-            }
-        }
-
-        @Override
-        public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
-            if (mMagnificationConnectionImpl != null) {
-                mMagnificationConnectionImpl.onPerformScaleAction(
-                        displayId, scale, updatePersistence);
-            }
-        }
-
-        @Override
-        public void onAccessibilityActionPerformed(int displayId) {
-            if (mMagnificationConnectionImpl != null) {
-                mMagnificationConnectionImpl.onAccessibilityActionPerformed(displayId);
-            }
-        }
-
-        @Override
-        public void onMove(int displayId) {
-            if (mMagnificationConnectionImpl != null) {
-                mMagnificationConnectionImpl.onMove(displayId);
-            }
-        }
-
-        @Override
-        public void onClickSettingsButton(int displayId) {
-            mHandler.post(() -> {
-                toggleSettingsPanelVisibility(displayId);
-            });
-        }
-    };
-
-    @VisibleForTesting
-    final MagnificationSettingsController.Callback mMagnificationSettingsControllerCallback =
-            new MagnificationSettingsController.Callback() {
-                @Override
-                public void onSetMagnifierSize(int displayId, int index) {
-                    mHandler.post(() -> onSetMagnifierSizeInternal(displayId, index));
-                    mA11yLogger.logWithPosition(
-                            MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED,
-                            index
-                    );
-                }
-
-                @Override
-                public void onSetDiagonalScrolling(int displayId, boolean enable) {
-                    mHandler.post(() -> onSetDiagonalScrollingInternal(displayId, enable));
-                }
-
-                @Override
-                public void onEditMagnifierSizeMode(int displayId, boolean enable) {
-                    mHandler.post(() -> onEditMagnifierSizeModeInternal(displayId, enable));
-                    mA11yLogger.log(enable
-                            ?
-                            MagnificationSettingsEvent
-                                    .MAGNIFICATION_SETTINGS_SIZE_EDITING_ACTIVATED
-                            : MagnificationSettingsEvent
-                                    .MAGNIFICATION_SETTINGS_SIZE_EDITING_DEACTIVATED);
-                }
-
-                @Override
-                public void onMagnifierScale(int displayId, float scale,
-                        boolean updatePersistence) {
-                    if (mMagnificationConnectionImpl != null) {
-                        mMagnificationConnectionImpl.onPerformScaleAction(
-                                displayId, scale, updatePersistence);
-                    }
-                    mA11yLogger.logThrottled(
-                            MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_ZOOM_SLIDER_CHANGED
-                    );
-                }
-
-                @Override
-                public void onModeSwitch(int displayId, int newMode) {
-                    mHandler.post(() -> onModeSwitchInternal(displayId, newMode));
-                }
-
-                @Override
-                public void onSettingsPanelVisibilityChanged(int displayId, boolean shown) {
-                    mHandler.post(() -> onSettingsPanelVisibilityChangedInternal(displayId, shown));
-                }
-            };
-
-    @MainThread
-    private void onSetMagnifierSizeInternal(int displayId, int index) {
-        final WindowMagnificationController windowMagnificationController =
-                mWindowMagnificationControllerSupplier.get(displayId);
-        if (windowMagnificationController != null) {
-            windowMagnificationController.changeMagnificationSize(index);
-        }
-    }
-
-    @MainThread
-    private void onSetDiagonalScrollingInternal(int displayId, boolean enable) {
-        final WindowMagnificationController windowMagnificationController =
-                mWindowMagnificationControllerSupplier.get(displayId);
-        if (windowMagnificationController != null) {
-            windowMagnificationController.setDiagonalScrolling(enable);
-        }
-    }
-
-    @MainThread
-    private void onEditMagnifierSizeModeInternal(int displayId, boolean enable) {
-        final WindowMagnificationController windowMagnificationController =
-                mWindowMagnificationControllerSupplier.get(displayId);
-        if (windowMagnificationController != null && windowMagnificationController.isActivated()) {
-            windowMagnificationController.setEditMagnifierSizeMode(enable);
-        }
-    }
-
-    @MainThread
-    private void onModeSwitchInternal(int displayId, int newMode) {
-        final WindowMagnificationController windowMagnificationController =
-                mWindowMagnificationControllerSupplier.get(displayId);
-        final boolean isWindowMagnifierActivated = windowMagnificationController.isActivated();
-        final boolean isSwitchToWindowMode = (newMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
-        final boolean changed = isSwitchToWindowMode ^ isWindowMagnifierActivated;
-        if (changed) {
-            final MagnificationSettingsController magnificationSettingsController =
-                    mMagnificationSettingsSupplier.get(displayId);
-            if (magnificationSettingsController != null) {
-                magnificationSettingsController.closeMagnificationSettings();
-            }
-            if (mMagnificationConnectionImpl != null) {
-                mMagnificationConnectionImpl.onChangeMagnificationMode(displayId, newMode);
-            }
-        }
-    }
-
-    @MainThread
-    private void onSettingsPanelVisibilityChangedInternal(int displayId, boolean shown) {
-        final WindowMagnificationController windowMagnificationController =
-                mWindowMagnificationControllerSupplier.get(displayId);
-        if (windowMagnificationController != null) {
-            boolean isWindowMagnifierActivated = windowMagnificationController.isActivated();
-            if (isWindowMagnifierActivated) {
-                windowMagnificationController.updateDragHandleResourcesIfNeeded(shown);
-            }
-
-            if (shown) {
-                mA11yLogger.logWithPosition(
-                        MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED,
-                        isWindowMagnifierActivated
-                                ? ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
-                                : ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN
-                );
-            } else {
-                mA11yLogger.log(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_CLOSED);
-            }
-        }
-    }
-
-    @Override
-    public void requestMagnificationConnection(boolean connect) {
-        if (connect) {
-            setMagnificationConnection();
-        } else {
-            clearMagnificationConnection();
-        }
-    }
-
-    @Override
-    public void dump(PrintWriter pw, String[] args) {
-        pw.println(TAG);
-        mWindowMagnificationControllerSupplier.forEach(
-                magnificationController -> magnificationController.dump(pw));
-    }
-
-    private void setMagnificationConnection() {
-        if (mMagnificationConnectionImpl == null) {
-            mMagnificationConnectionImpl = new MagnificationConnectionImpl(this,
-                    mHandler);
-        }
-        mAccessibilityManager.setMagnificationConnection(
-                mMagnificationConnectionImpl);
-    }
-
-    private void clearMagnificationConnection() {
-        mAccessibilityManager.setMagnificationConnection(null);
-        //TODO: destroy controllers.
-    }
+    void moveWindowMagnifierToPosition(
+            int displayId,
+            float positionX,
+            float positionY,
+            IRemoteMagnificationAnimationCallback callback);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java
index b5f3aef..c8b53ee 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java
@@ -32,7 +32,7 @@
  *
  * @see IMagnificationConnection
  */
-class MagnificationConnectionImpl extends IMagnificationConnection.Stub {
+public class MagnificationConnectionImpl extends IMagnificationConnection.Stub {
 
     private static final String TAG = "WindowMagnificationConnectionImpl";
 
@@ -40,7 +40,7 @@
     private final Magnification mMagnification;
     private final Handler mHandler;
 
-    MagnificationConnectionImpl(@NonNull Magnification magnification,
+    public MagnificationConnectionImpl(@NonNull Magnification magnification,
             @Main Handler mainHandler) {
         mMagnification = magnification;
         mHandler = mainHandler;
@@ -83,7 +83,7 @@
     @Override
     public void moveWindowMagnifierToPosition(int displayId, float positionX, float positionY,
             IRemoteMagnificationAnimationCallback callback) {
-        mHandler.post(() -> mMagnification.moveWindowMagnifierToPositionInternal(
+        mHandler.post(() -> mMagnification.moveWindowMagnifierToPosition(
                 displayId, positionX, positionY, callback));
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
new file mode 100644
index 0000000..6e5e44e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
@@ -0,0 +1,641 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+
+import static com.android.systemui.accessibility.AccessibilityLogger.MagnificationSettingsEvent;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
+
+import android.annotation.MainThread;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.IWindowManager;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IMagnificationConnection;
+import android.view.accessibility.IRemoteMagnificationAnimationCallback;
+import android.window.InputTransferToken;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.Flags;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.util.settings.SecureSettings;
+
+import java.io.PrintWriter;
+import java.util.concurrent.Executor;
+import java.util.function.Supplier;
+
+import javax.inject.Inject;
+
+/**
+ * Class to handle the interaction with
+ * {@link com.android.server.accessibility.AccessibilityManagerService}. It invokes
+ * {@link AccessibilityManager#setMagnificationConnection(IMagnificationConnection)}
+ * when {@code IStatusBar#requestWindowMagnificationConnection(boolean)} is called.
+ */
+@SysUISingleton
+public class MagnificationImpl implements Magnification, CommandQueue.Callbacks {
+    private static final String TAG = "Magnification";
+
+    @VisibleForTesting static final int DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS = 300;
+    private static final int MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL = 1;
+
+    private final ModeSwitchesController mModeSwitchesController;
+    private final Handler mHandler;
+    private final Executor mExecutor;
+    private final AccessibilityManager mAccessibilityManager;
+    private final CommandQueue mCommandQueue;
+    private final OverviewProxyService mOverviewProxyService;
+    private final DisplayTracker mDisplayTracker;
+    private final AccessibilityLogger mA11yLogger;
+
+    private MagnificationConnectionImpl mMagnificationConnectionImpl;
+    private SysUiState mSysUiState;
+
+    @VisibleForTesting
+    SparseArray<SparseArray<Float>> mUsersScales = new SparseArray();
+
+    private static class WindowMagnificationControllerSupplier extends
+            DisplayIdIndexSupplier<WindowMagnificationController> {
+
+        private final Context mContext;
+        private final Handler mHandler;
+        private final WindowMagnifierCallback mWindowMagnifierCallback;
+        private final SysUiState mSysUiState;
+        private final SecureSettings mSecureSettings;
+
+        WindowMagnificationControllerSupplier(Context context, Handler handler,
+                WindowMagnifierCallback windowMagnifierCallback,
+                DisplayManager displayManager, SysUiState sysUiState,
+                SecureSettings secureSettings) {
+            super(displayManager);
+            mContext = context;
+            mHandler = handler;
+            mWindowMagnifierCallback = windowMagnifierCallback;
+            mSysUiState = sysUiState;
+            mSecureSettings = secureSettings;
+        }
+
+        @Override
+        protected WindowMagnificationController createInstance(Display display) {
+            final Context windowContext = mContext.createWindowContext(display,
+                    Flags.createWindowlessWindowMagnifier()
+                            ? TYPE_ACCESSIBILITY_OVERLAY
+                            : TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
+                    /* options */ null);
+            windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
+
+            Supplier<SurfaceControlViewHost> scvhSupplier = () ->
+                    Flags.createWindowlessWindowMagnifier() ? new SurfaceControlViewHost(mContext,
+                            mContext.getDisplay(), new InputTransferToken(), TAG) : null;
+
+            return new WindowMagnificationController(
+                    windowContext,
+                    mHandler,
+                    new WindowMagnificationAnimationController(windowContext),
+                    /* mirrorWindowControl= */ null,
+                    new SurfaceControl.Transaction(),
+                    mWindowMagnifierCallback,
+                    mSysUiState,
+                    mSecureSettings,
+                    scvhSupplier,
+                    new SfVsyncFrameCallbackProvider(),
+                    WindowManagerGlobal::getWindowSession);
+        }
+    }
+
+    @VisibleForTesting
+    DisplayIdIndexSupplier<WindowMagnificationController> mWindowMagnificationControllerSupplier;
+
+    private static class FullscreenMagnificationControllerSupplier extends
+            DisplayIdIndexSupplier<FullscreenMagnificationController> {
+
+        private final Context mContext;
+        private final Handler mHandler;
+        private final Executor mExecutor;
+        private final IWindowManager mIWindowManager;
+
+        FullscreenMagnificationControllerSupplier(Context context,
+                DisplayManager displayManager,
+                Handler handler,
+                Executor executor, IWindowManager iWindowManager) {
+            super(displayManager);
+            mContext = context;
+            mHandler = handler;
+            mExecutor = executor;
+            mIWindowManager = iWindowManager;
+        }
+
+        @Override
+        protected FullscreenMagnificationController createInstance(Display display) {
+            final Context windowContext = mContext.createWindowContext(display,
+                    TYPE_ACCESSIBILITY_OVERLAY, /* options */ null);
+            Supplier<SurfaceControlViewHost> scvhSupplier = () -> new SurfaceControlViewHost(
+                    mContext, mContext.getDisplay(), new InputTransferToken(), TAG);
+            windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
+            return new FullscreenMagnificationController(
+                    windowContext,
+                    mHandler,
+                    mExecutor,
+                    windowContext.getSystemService(AccessibilityManager.class),
+                    windowContext.getSystemService(WindowManager.class),
+                    mIWindowManager,
+                    scvhSupplier);
+        }
+    }
+
+    @VisibleForTesting
+    DisplayIdIndexSupplier<FullscreenMagnificationController>
+            mFullscreenMagnificationControllerSupplier;
+
+    private static class SettingsSupplier extends
+            DisplayIdIndexSupplier<MagnificationSettingsController> {
+
+        private final Context mContext;
+        private final MagnificationSettingsController.Callback mSettingsControllerCallback;
+        private final SecureSettings mSecureSettings;
+
+        SettingsSupplier(Context context,
+                MagnificationSettingsController.Callback settingsControllerCallback,
+                DisplayManager displayManager,
+                SecureSettings secureSettings) {
+            super(displayManager);
+            mContext = context;
+            mSettingsControllerCallback = settingsControllerCallback;
+            mSecureSettings = secureSettings;
+        }
+
+        @Override
+        protected MagnificationSettingsController createInstance(Display display) {
+            final Context windowContext = mContext.createWindowContext(display,
+                    TYPE_ACCESSIBILITY_OVERLAY, /* options */ null);
+            windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
+            return new MagnificationSettingsController(
+                    windowContext,
+                    new SfVsyncFrameCallbackProvider(),
+                    mSettingsControllerCallback,
+                    mSecureSettings);
+        }
+    }
+
+    @VisibleForTesting
+    DisplayIdIndexSupplier<MagnificationSettingsController> mMagnificationSettingsSupplier;
+
+    @Inject
+    public MagnificationImpl(Context context,
+            @Main Handler mainHandler, @Main Executor executor,
+            CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
+            SysUiState sysUiState, OverviewProxyService overviewProxyService,
+            SecureSettings secureSettings, DisplayTracker displayTracker,
+            DisplayManager displayManager, AccessibilityLogger a11yLogger,
+            IWindowManager iWindowManager, AccessibilityManager accessibilityManager) {
+        this(context, mainHandler.getLooper(), executor, commandQueue,
+                modeSwitchesController, sysUiState, overviewProxyService, secureSettings,
+                displayTracker, displayManager, a11yLogger, iWindowManager, accessibilityManager);
+    }
+
+    @VisibleForTesting
+    public MagnificationImpl(Context context, Looper looper, @Main Executor executor,
+            CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
+            SysUiState sysUiState, OverviewProxyService overviewProxyService,
+            SecureSettings secureSettings, DisplayTracker displayTracker,
+            DisplayManager displayManager, AccessibilityLogger a11yLogger,
+            IWindowManager iWindowManager,
+            AccessibilityManager accessibilityManager) {
+        mHandler = new Handler(looper) {
+            @Override
+            public void handleMessage(@NonNull Message msg) {
+                if (msg.what == MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL) {
+                    showMagnificationButtonInternal(msg.arg1, msg.arg2);
+                }
+            }
+        };
+        mExecutor = executor;
+        mAccessibilityManager = accessibilityManager;
+        mCommandQueue = commandQueue;
+        mModeSwitchesController = modeSwitchesController;
+        mSysUiState = sysUiState;
+        mOverviewProxyService = overviewProxyService;
+        mDisplayTracker = displayTracker;
+        mA11yLogger = a11yLogger;
+        mWindowMagnificationControllerSupplier = new WindowMagnificationControllerSupplier(context,
+                mHandler, mWindowMagnifierCallback,
+                displayManager, sysUiState, secureSettings);
+        mFullscreenMagnificationControllerSupplier = new FullscreenMagnificationControllerSupplier(
+                context, displayManager, mHandler, mExecutor, iWindowManager);
+        mMagnificationSettingsSupplier = new SettingsSupplier(context,
+                mMagnificationSettingsControllerCallback, displayManager, secureSettings);
+
+        mModeSwitchesController.setClickListenerDelegate(
+                displayId -> mHandler.post(() -> {
+                    toggleSettingsPanelVisibility(displayId);
+                }));
+    }
+
+    @Override
+    public void start() {
+        mCommandQueue.addCallback(this);
+        mOverviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() {
+            @Override
+            public void onConnectionChanged(boolean isConnected) {
+                if (isConnected) {
+                    updateSysUiStateFlag();
+                }
+            }
+        });
+    }
+
+    private void updateSysUiStateFlag() {
+        //TODO(b/187510533): support multi-display once SysuiState supports it.
+        final WindowMagnificationController controller =
+                mWindowMagnificationControllerSupplier.valueAt(
+                        mDisplayTracker.getDefaultDisplayId());
+        if (controller != null) {
+            controller.updateSysUIStateFlag();
+        } else {
+            // The instance is initialized when there is an IPC request. Considering
+            // self-crash cases, we need to reset the flag in such situation.
+            mSysUiState.setFlag(SYSUI_STATE_MAGNIFICATION_OVERLAP, false)
+                    .commitUpdate(mDisplayTracker.getDefaultDisplayId());
+        }
+    }
+
+    @Override
+    @MainThread
+    public void enableWindowMagnification(int displayId, float scale, float centerX, float centerY,
+            float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY,
+            @Nullable IRemoteMagnificationAnimationCallback callback) {
+        final WindowMagnificationController windowMagnificationController =
+                mWindowMagnificationControllerSupplier.get(displayId);
+        if (windowMagnificationController != null) {
+            windowMagnificationController.enableWindowMagnification(scale, centerX, centerY,
+                    magnificationFrameOffsetRatioX, magnificationFrameOffsetRatioY, callback);
+        }
+    }
+
+    @Override
+    @MainThread
+    public void setScaleForWindowMagnification(int displayId, float scale) {
+        final WindowMagnificationController windowMagnificationController =
+                mWindowMagnificationControllerSupplier.get(displayId);
+        if (windowMagnificationController != null) {
+            windowMagnificationController.setScale(scale);
+        }
+    }
+
+    @Override
+    @MainThread
+    public void moveWindowMagnifier(int displayId, float offsetX, float offsetY) {
+        final WindowMagnificationController windowMagnificationcontroller =
+                mWindowMagnificationControllerSupplier.get(displayId);
+        if (windowMagnificationcontroller != null) {
+            windowMagnificationcontroller.moveWindowMagnifier(offsetX, offsetY);
+        }
+    }
+
+    @Override
+    @MainThread
+    public void moveWindowMagnifierToPosition(int displayId, float positionX, float positionY,
+            IRemoteMagnificationAnimationCallback callback) {
+        final WindowMagnificationController windowMagnificationController =
+                mWindowMagnificationControllerSupplier.get(displayId);
+        if (windowMagnificationController != null) {
+            windowMagnificationController.moveWindowMagnifierToPosition(positionX, positionY,
+                    callback);
+        }
+    }
+
+    @Override
+    @MainThread
+    public void disableWindowMagnification(int displayId,
+            @Nullable IRemoteMagnificationAnimationCallback callback) {
+        final WindowMagnificationController windowMagnificationController =
+                mWindowMagnificationControllerSupplier.get(displayId);
+        if (windowMagnificationController != null) {
+            windowMagnificationController.deleteWindowMagnification(callback);
+        }
+    }
+
+    @Override
+    @MainThread
+    public void onFullscreenMagnificationActivationChanged(int displayId, boolean activated) {
+        final FullscreenMagnificationController fullscreenMagnificationController =
+                mFullscreenMagnificationControllerSupplier.get(displayId);
+        if (fullscreenMagnificationController != null) {
+            fullscreenMagnificationController.onFullscreenMagnificationActivationChanged(activated);
+        }
+    }
+
+    @MainThread
+    void toggleSettingsPanelVisibility(int displayId) {
+        final MagnificationSettingsController magnificationSettingsController =
+                mMagnificationSettingsSupplier.get(displayId);
+        if (magnificationSettingsController != null) {
+            magnificationSettingsController.toggleSettingsPanelVisibility();
+        }
+    }
+
+    @Override
+    @MainThread
+    public void hideMagnificationSettingsPanel(int displayId) {
+        final MagnificationSettingsController magnificationSettingsController =
+                mMagnificationSettingsSupplier.get(displayId);
+        if (magnificationSettingsController != null) {
+            magnificationSettingsController.closeMagnificationSettings();
+        }
+    }
+
+    boolean isMagnificationSettingsPanelShowing(int displayId) {
+        final MagnificationSettingsController magnificationSettingsController =
+                mMagnificationSettingsSupplier.get(displayId);
+        if (magnificationSettingsController != null) {
+            return magnificationSettingsController.isMagnificationSettingsShowing();
+        }
+        return false;
+    }
+
+    @Override
+    @MainThread
+    public void showMagnificationButton(int displayId, int magnificationMode) {
+        if (Flags.delayShowMagnificationButton()) {
+            if (mHandler.hasMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL)) {
+                return;
+            }
+            mHandler.sendMessageDelayed(
+                    mHandler.obtainMessage(
+                            MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL, displayId, magnificationMode),
+                    DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS);
+        } else {
+            showMagnificationButtonInternal(displayId, magnificationMode);
+        }
+    }
+
+    @MainThread
+    private void showMagnificationButtonInternal(int displayId, int magnificationMode) {
+        // not to show mode switch button if settings panel is already showing to
+        // prevent settings panel be covered by the button.
+        if (isMagnificationSettingsPanelShowing(displayId)) {
+            return;
+        }
+        mModeSwitchesController.showButton(displayId, magnificationMode);
+    }
+
+    @Override
+    @MainThread
+    public void removeMagnificationButton(int displayId) {
+        if (Flags.delayShowMagnificationButton()) {
+            mHandler.removeMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL);
+        }
+        mModeSwitchesController.removeButton(displayId);
+    }
+
+    @Override
+    @MainThread
+    public void setUserMagnificationScale(int userId, int displayId, float scale) {
+        SparseArray<Float> scales = mUsersScales.get(userId);
+        if (scales == null) {
+            scales = new SparseArray<>();
+            mUsersScales.put(userId, scales);
+        }
+        if (scales.contains(displayId) && scales.get(displayId) == scale) {
+            return;
+        }
+        scales.put(displayId, scale);
+
+        final MagnificationSettingsController magnificationSettingsController =
+                mMagnificationSettingsSupplier.get(displayId);
+        magnificationSettingsController.setMagnificationScale(scale);
+    }
+
+    @VisibleForTesting
+    final WindowMagnifierCallback mWindowMagnifierCallback = new WindowMagnifierCallback() {
+        @Override
+        public void onWindowMagnifierBoundsChanged(int displayId, Rect frame) {
+            if (mMagnificationConnectionImpl != null) {
+                mMagnificationConnectionImpl.onWindowMagnifierBoundsChanged(displayId, frame);
+            }
+        }
+
+        @Override
+        public void onSourceBoundsChanged(int displayId, Rect sourceBounds) {
+            if (mMagnificationConnectionImpl != null) {
+                mMagnificationConnectionImpl.onSourceBoundsChanged(displayId, sourceBounds);
+            }
+        }
+
+        @Override
+        public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
+            if (mMagnificationConnectionImpl != null) {
+                mMagnificationConnectionImpl.onPerformScaleAction(
+                        displayId, scale, updatePersistence);
+            }
+        }
+
+        @Override
+        public void onAccessibilityActionPerformed(int displayId) {
+            if (mMagnificationConnectionImpl != null) {
+                mMagnificationConnectionImpl.onAccessibilityActionPerformed(displayId);
+            }
+        }
+
+        @Override
+        public void onMove(int displayId) {
+            if (mMagnificationConnectionImpl != null) {
+                mMagnificationConnectionImpl.onMove(displayId);
+            }
+        }
+
+        @Override
+        public void onClickSettingsButton(int displayId) {
+            mHandler.post(() -> {
+                toggleSettingsPanelVisibility(displayId);
+            });
+        }
+    };
+
+    @VisibleForTesting
+    final MagnificationSettingsController.Callback mMagnificationSettingsControllerCallback =
+            new MagnificationSettingsController.Callback() {
+                @Override
+                public void onSetMagnifierSize(int displayId, int index) {
+                    mHandler.post(() -> onSetMagnifierSizeInternal(displayId, index));
+                    mA11yLogger.logWithPosition(
+                            MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED,
+                            index
+                    );
+                }
+
+                @Override
+                public void onSetDiagonalScrolling(int displayId, boolean enable) {
+                    mHandler.post(() -> onSetDiagonalScrollingInternal(displayId, enable));
+                }
+
+                @Override
+                public void onEditMagnifierSizeMode(int displayId, boolean enable) {
+                    mHandler.post(() -> onEditMagnifierSizeModeInternal(displayId, enable));
+                    mA11yLogger.log(enable
+                            ?
+                            MagnificationSettingsEvent
+                                    .MAGNIFICATION_SETTINGS_SIZE_EDITING_ACTIVATED
+                            : MagnificationSettingsEvent
+                                    .MAGNIFICATION_SETTINGS_SIZE_EDITING_DEACTIVATED);
+                }
+
+                @Override
+                public void onMagnifierScale(int displayId, float scale,
+                        boolean updatePersistence) {
+                    if (mMagnificationConnectionImpl != null) {
+                        mMagnificationConnectionImpl.onPerformScaleAction(
+                                displayId, scale, updatePersistence);
+                    }
+                    mA11yLogger.logThrottled(
+                            MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_ZOOM_SLIDER_CHANGED
+                    );
+                }
+
+                @Override
+                public void onModeSwitch(int displayId, int newMode) {
+                    mHandler.post(() -> onModeSwitchInternal(displayId, newMode));
+                }
+
+                @Override
+                public void onSettingsPanelVisibilityChanged(int displayId, boolean shown) {
+                    mHandler.post(() -> onSettingsPanelVisibilityChangedInternal(displayId, shown));
+                }
+            };
+
+    @MainThread
+    private void onSetMagnifierSizeInternal(int displayId, int index) {
+        final WindowMagnificationController windowMagnificationController =
+                mWindowMagnificationControllerSupplier.get(displayId);
+        if (windowMagnificationController != null) {
+            windowMagnificationController.changeMagnificationSize(index);
+        }
+    }
+
+    @MainThread
+    private void onSetDiagonalScrollingInternal(int displayId, boolean enable) {
+        final WindowMagnificationController windowMagnificationController =
+                mWindowMagnificationControllerSupplier.get(displayId);
+        if (windowMagnificationController != null) {
+            windowMagnificationController.setDiagonalScrolling(enable);
+        }
+    }
+
+    @MainThread
+    private void onEditMagnifierSizeModeInternal(int displayId, boolean enable) {
+        final WindowMagnificationController windowMagnificationController =
+                mWindowMagnificationControllerSupplier.get(displayId);
+        if (windowMagnificationController != null && windowMagnificationController.isActivated()) {
+            windowMagnificationController.setEditMagnifierSizeMode(enable);
+        }
+    }
+
+    @MainThread
+    private void onModeSwitchInternal(int displayId, int newMode) {
+        final WindowMagnificationController windowMagnificationController =
+                mWindowMagnificationControllerSupplier.get(displayId);
+        final boolean isWindowMagnifierActivated = windowMagnificationController.isActivated();
+        final boolean isSwitchToWindowMode = (newMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        final boolean changed = isSwitchToWindowMode ^ isWindowMagnifierActivated;
+        if (changed) {
+            final MagnificationSettingsController magnificationSettingsController =
+                    mMagnificationSettingsSupplier.get(displayId);
+            if (magnificationSettingsController != null) {
+                magnificationSettingsController.closeMagnificationSettings();
+            }
+            if (mMagnificationConnectionImpl != null) {
+                mMagnificationConnectionImpl.onChangeMagnificationMode(displayId, newMode);
+            }
+        }
+    }
+
+    @MainThread
+    private void onSettingsPanelVisibilityChangedInternal(int displayId, boolean shown) {
+        final WindowMagnificationController windowMagnificationController =
+                mWindowMagnificationControllerSupplier.get(displayId);
+        if (windowMagnificationController != null) {
+            boolean isWindowMagnifierActivated = windowMagnificationController.isActivated();
+            if (isWindowMagnifierActivated) {
+                windowMagnificationController.updateDragHandleResourcesIfNeeded(shown);
+            }
+
+            if (shown) {
+                mA11yLogger.logWithPosition(
+                        MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED,
+                        isWindowMagnifierActivated
+                                ? ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
+                                : ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN
+                );
+            } else {
+                mA11yLogger.log(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_CLOSED);
+            }
+        }
+    }
+
+    @Override
+    public void requestMagnificationConnection(boolean connect) {
+        if (connect) {
+            setMagnificationConnection();
+        } else {
+            clearMagnificationConnection();
+        }
+    }
+
+    @Override
+    public void dump(PrintWriter pw, String[] args) {
+        pw.println(TAG);
+        mWindowMagnificationControllerSupplier.forEach(
+                magnificationController -> magnificationController.dump(pw));
+    }
+
+    private void setMagnificationConnection() {
+        if (mMagnificationConnectionImpl == null) {
+            mMagnificationConnectionImpl = new MagnificationConnectionImpl(this,
+                    mHandler);
+        }
+        mAccessibilityManager.setMagnificationConnection(
+                mMagnificationConnectionImpl);
+    }
+
+    private void clearMagnificationConnection() {
+        mAccessibilityManager.setMagnificationConnection(null);
+        //TODO: destroy controllers.
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index ac8807d..3904ee1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -50,6 +50,7 @@
 import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
 import com.android.systemui.res.R
@@ -132,11 +133,11 @@
             R.dimen.biometric_prompt_landscape_medium_horizontal_padding
         )
 
+    val udfpsOverlayParams: StateFlow<UdfpsOverlayParams> =
+        udfpsOverlayInteractor.udfpsOverlayParams
+
     private val udfpsSensorBounds: Flow<Rect> =
-        combine(
-                udfpsOverlayInteractor.udfpsOverlayParams,
-                displayStateInteractor.currentRotation
-            ) { params, rotation ->
+        combine(udfpsOverlayParams, displayStateInteractor.currentRotation) { params, rotation ->
                 val rotatedBounds = Rect(params.sensorBounds)
                 RotationUtils.rotateBounds(
                     rotatedBounds,
@@ -153,32 +154,27 @@
             }
             .distinctUntilChanged()
 
+    private val udfpsSensorWidth: Flow<Int> = udfpsOverlayParams.map { it.sensorBounds.width() }
+    private val udfpsSensorHeight: Flow<Int> = udfpsOverlayParams.map { it.sensorBounds.height() }
+
     val legacyFingerprintSensorWidth: Flow<Int> =
-        combine(modalities, udfpsOverlayInteractor.udfpsOverlayParams) { modalities, overlayParams
-            ->
+        combine(modalities, udfpsSensorWidth) { modalities, udfpsSensorWidth ->
             if (modalities.hasUdfps) {
-                overlayParams.sensorBounds.width()
+                udfpsSensorWidth
             } else {
                 fingerprintIconWidth
             }
         }
 
     val legacyFingerprintSensorHeight: Flow<Int> =
-        combine(modalities, udfpsOverlayInteractor.udfpsOverlayParams) { modalities, overlayParams
-            ->
+        combine(modalities, udfpsSensorHeight) { modalities, udfpsSensorHeight ->
             if (modalities.hasUdfps) {
-                overlayParams.sensorBounds.height()
+                udfpsSensorHeight
             } else {
                 fingerprintIconHeight
             }
         }
 
-    val fingerprintSensorWidth: Int =
-        udfpsOverlayInteractor.udfpsOverlayParams.value.sensorBounds.width()
-
-    val fingerprintSensorHeight: Int =
-        udfpsOverlayInteractor.udfpsOverlayParams.value.sensorBounds.height()
-
     private val _accessibilityHint = MutableSharedFlow<String>()
 
     /** Hint for talkback directional guidance */
@@ -442,12 +438,16 @@
 
     /** The size of the biometric icon */
     val iconSize: Flow<Pair<Int, Int>> =
-        combine(iconViewModel.activeAuthType, modalities) { activeAuthType, modalities ->
+        combine(iconViewModel.activeAuthType, modalities, udfpsSensorWidth, udfpsSensorHeight) {
+            activeAuthType,
+            modalities,
+            udfpsSensorWidth,
+            udfpsSensorHeight ->
             if (activeAuthType == PromptIconViewModel.AuthType.Face) {
                 Pair(faceIconWidth, faceIconHeight)
             } else {
                 if (modalities.hasUdfps) {
-                    Pair(fingerprintSensorWidth, fingerprintSensorHeight)
+                    Pair(udfpsSensorWidth, udfpsSensorHeight)
                 } else {
                     Pair(fingerprintIconWidth, fingerprintIconHeight)
                 }
@@ -921,13 +921,13 @@
                 udfpsUtils.getTouchInNativeCoordinates(
                     event.getPointerId(0),
                     event,
-                    udfpsOverlayInteractor.udfpsOverlayParams.value
+                    udfpsOverlayParams.value
                 )
             if (
                 !udfpsUtils.isWithinSensorArea(
                     event.getPointerId(0),
                     event,
-                    udfpsOverlayInteractor.udfpsOverlayParams.value
+                    udfpsOverlayParams.value
                 )
             ) {
                 _accessibilityHint.emit(
@@ -936,7 +936,7 @@
                         context,
                         scaledTouch.x,
                         scaledTouch.y,
-                        udfpsOverlayInteractor.udfpsOverlayParams.value
+                        udfpsOverlayParams.value
                     )
                 )
             }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index cc90730..6ec6ec1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -129,7 +129,6 @@
 
     /** Called as the UI requests opening the widget editor with an optional preselected widget. */
     open fun onOpenWidgetEditor(
-        preselectedKey: String? = null,
         shouldOpenWidgetPickerOnStart: Boolean = false,
     ) {}
 
@@ -146,7 +145,7 @@
     open fun onReorderWidgetCancel() {}
 
     /** Called as the user request to show the customize widget button. */
-    open fun onShowCustomizeWidgetButton() {}
+    open fun onLongClick() {}
 
     /** Set the key of the currently selected item */
     fun setSelectedKey(key: String?) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 11247df..2043dd1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -40,6 +40,7 @@
 import com.android.systemui.media.dagger.MediaModule
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.KeyguardIndicationController
 import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
@@ -76,9 +77,10 @@
     @Main private val resources: Resources,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     keyguardInteractor: KeyguardInteractor,
+    private val keyguardIndicationController: KeyguardIndicationController,
     communalSceneInteractor: CommunalSceneInteractor,
     private val communalInteractor: CommunalInteractor,
-    private val communalSettingsInteractor: CommunalSettingsInteractor,
+    communalSettingsInteractor: CommunalSettingsInteractor,
     tutorialInteractor: CommunalTutorialInteractor,
     private val shadeInteractor: ShadeInteractor,
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
@@ -218,9 +220,8 @@
     }
 
     override fun onOpenWidgetEditor(
-        preselectedKey: String?,
         shouldOpenWidgetPickerOnStart: Boolean,
-    ) = communalInteractor.showWidgetEditor(preselectedKey, shouldOpenWidgetPickerOnStart)
+    ) = communalInteractor.showWidgetEditor(selectedKey.value, shouldOpenWidgetPickerOnStart)
 
     override fun onDismissCtaTile() {
         scope.launch {
@@ -229,7 +230,11 @@
         }
     }
 
-    override fun onShowCustomizeWidgetButton() {
+    fun onClick() {
+        keyguardIndicationController.showActionToUnlock()
+    }
+
+    override fun onLongClick() {
         setCurrentPopupType(PopupType.CustomizeWidgetButton)
     }
 
@@ -319,7 +324,7 @@
 }
 
 sealed class PopupType {
-    object CtaTile : PopupType()
+    data object CtaTile : PopupType()
 
-    object CustomizeWidgetButton : PopupType()
+    data object CustomizeWidgetButton : PopupType()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 9f0fc51..9ae63a1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -25,7 +25,9 @@
 import com.android.keyguard.KeyguardViewController;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.ScreenDecorationsModule;
+import com.android.systemui.accessibility.AccessibilityModule;
 import com.android.systemui.accessibility.SystemActionsModule;
+import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
 import com.android.systemui.battery.BatterySaverModule;
 import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel;
 import com.android.systemui.dock.DockManager;
@@ -107,6 +109,8 @@
  * SystemUI code that variants of SystemUI _must_ include to function correctly.
  */
 @Module(includes = {
+        AccessibilityModule.class,
+        AccessibilityRepositoryModule.class,
         AospPolicyModule.class,
         BatterySaverModule.class,
         CollapsedStatusBarFragmentStartableModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index a7ff3c3..4b2fb44 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -32,8 +32,6 @@
 import com.android.systemui.CameraProtectionModule;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.SystemUISecondaryUserService;
-import com.android.systemui.accessibility.AccessibilityModule;
-import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
 import com.android.systemui.ambient.dagger.AmbientModule;
 import com.android.systemui.appops.dagger.AppOpsModule;
 import com.android.systemui.assist.AssistModule;
@@ -187,8 +185,6 @@
  * may not appreciate that.
  */
 @Module(includes = {
-        AccessibilityModule.class,
-        AccessibilityRepositoryModule.class,
         AmbientModule.class,
         AppOpsModule.class,
         AssistModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 10d6881..4c7142b 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -211,6 +211,9 @@
             }
         }
 
+    /** Whether the device is in lockdown mode, where bouncer input is required to unlock. */
+    val isInLockdown: Flow<Boolean> = deviceEntryRestrictionReason.map { it.isInLockdown() }
+
     /**
      * Attempt to enter the device and dismiss the lockscreen. If authentication is required to
      * unlock the device it will transition to bouncer.
@@ -259,6 +262,27 @@
         return repository.isLockscreenEnabled()
     }
 
+    fun DeviceEntryRestrictionReason?.isInLockdown(): Boolean {
+        return when (this) {
+            DeviceEntryRestrictionReason.UserLockdown -> true
+            DeviceEntryRestrictionReason.PolicyLockdown -> true
+
+            // Add individual enum value instead of using "else" so new reasons are guaranteed
+            // to be added here at compile-time.
+            null -> false
+            DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot -> false
+            DeviceEntryRestrictionReason.BouncerLockedOut -> false
+            DeviceEntryRestrictionReason.AdaptiveAuthRequest -> false
+            DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout -> false
+            DeviceEntryRestrictionReason.TrustAgentDisabled -> false
+            DeviceEntryRestrictionReason.StrongBiometricsLockedOut -> false
+            DeviceEntryRestrictionReason.SecurityTimeout -> false
+            DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate -> false
+            DeviceEntryRestrictionReason.UnattendedUpdate -> false
+            DeviceEntryRestrictionReason.NonStrongFaceLockedOut -> false
+        }
+    }
+
     /**
      * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically
      * dismissed once the authentication challenge is completed. For example, completing a biometric
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index cdf3b06..888d047 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardDone
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.time.SystemClock
@@ -293,6 +294,15 @@
 
     /** Sets whether the keyguard is enabled (see [isKeyguardEnabled]). */
     fun setKeyguardEnabled(enabled: Boolean)
+
+    /** @see isShowKeyguardWhenReenabled */
+    fun setShowKeyguardWhenReenabled(isShowKeyguardWhenReenabled: Boolean)
+
+    /**
+     * Returns `true` if the keyguard should be re-shown once it becomes re-enabled again; `false`
+     * otherwise.
+     */
+    fun isShowKeyguardWhenReenabled(): Boolean
 }
 
 /** Encapsulates application state for the keyguard. */
@@ -474,6 +484,8 @@
     private val _isDozing = MutableStateFlow(statusBarStateController.isDozing)
     override val isDozing: StateFlow<Boolean> = _isDozing.asStateFlow()
 
+    private var isShowKeyguardWhenReenabled: Boolean = false
+
     override fun setIsDozing(isDozing: Boolean) {
         _isDozing.value = isDozing
     }
@@ -692,6 +704,16 @@
         _isKeyguardEnabled.value = enabled
     }
 
+    override fun setShowKeyguardWhenReenabled(isShowKeyguardWhenReenabled: Boolean) {
+        SceneContainerFlag.assertInNewMode()
+        this.isShowKeyguardWhenReenabled = isShowKeyguardWhenReenabled
+    }
+
+    override fun isShowKeyguardWhenReenabled(): Boolean {
+        SceneContainerFlag.assertInNewMode()
+        return isShowKeyguardWhenReenabled
+    }
+
     private fun statusBarStateIntToObject(value: Int): StatusBarState {
         return when (value) {
             0 -> StatusBarState.SHADE
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 2f40c99..506a18d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -232,5 +232,6 @@
         val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION
         val TO_GONE_DURATION = DEFAULT_DURATION
         val TO_PRIMARY_BOUNCER_DURATION = DEFAULT_DURATION
+        val TO_GLANCEABLE_HUB_DURATION = DEFAULT_DURATION
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index f5b12a2..07a2b04 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -99,19 +99,21 @@
                     }
             }
 
-            scope.launch {
-                keyguardRepository.isKeyguardEnabled
-                    .filterRelevantKeyguardStateAnd { enabled -> enabled }
-                    .sample(keyguardEnabledInteractor.showKeyguardWhenReenabled)
-                    .filter { reshow -> reshow }
-                    .collect {
-                        startTransitionTo(
-                            KeyguardState.LOCKSCREEN,
-                            ownerReason =
-                                "Keyguard was re-enabled, and we weren't GONE when it " +
-                                    "was originally disabled"
-                        )
-                    }
+            if (!SceneContainerFlag.isEnabled) {
+                scope.launch {
+                    keyguardRepository.isKeyguardEnabled
+                        .filterRelevantKeyguardStateAnd { enabled -> enabled }
+                        .sample(keyguardEnabledInteractor.showKeyguardWhenReenabled)
+                        .filter { reshow -> reshow }
+                        .collect {
+                            startTransitionTo(
+                                KeyguardState.LOCKSCREEN,
+                                ownerReason =
+                                    "Keyguard was re-enabled, and we weren't GONE when it " +
+                                        "was originally disabled"
+                            )
+                        }
+                }
             }
         } else {
             scope.launch("$TAG#listenForGoneToLockscreenOrHub") {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index f8208b3..f23b12f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -233,6 +233,7 @@
                     KeyguardState.DOZING -> TO_DOZING_DURATION
                     KeyguardState.GONE -> TO_GONE_DURATION
                     KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+                    KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION
                     else -> DEFAULT_DURATION
                 }.inWholeMilliseconds
         }
@@ -245,6 +246,7 @@
         val TO_GONE_DURATION = 500.milliseconds
         val TO_GONE_SHORT_DURATION = 200.milliseconds
         val TO_LOCKSCREEN_DURATION = 450.milliseconds
+        val TO_GLANCEABLE_HUB_DURATION = DEFAULT_DURATION
         val TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD = 0.5f
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
index 9cc0b3c..8d683f0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -28,6 +29,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 
 /**
@@ -68,6 +70,7 @@
      */
     val showKeyguardWhenReenabled: Flow<Boolean> =
         repository.isKeyguardEnabled
+            .onEach { SceneContainerFlag.assertInLegacyMode() }
             // Whenever the keyguard is disabled...
             .filter { enabled -> !enabled }
             .sampleCombine(
@@ -103,4 +106,12 @@
     fun notifyKeyguardEnabled(enabled: Boolean) {
         repository.setKeyguardEnabled(enabled)
     }
+
+    fun setShowKeyguardWhenReenabled(isShowKeyguardWhenReenabled: Boolean) {
+        repository.setShowKeyguardWhenReenabled(isShowKeyguardWhenReenabled)
+    }
+
+    fun isShowKeyguardWhenReenabled(): Boolean {
+        return repository.isShowKeyguardWhenReenabled()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
index ecdc21c..dc7a649 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodToPrimaryBouncerTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DozingToGlanceableHubTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DozingToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel
@@ -49,6 +50,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.OffToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToAodTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToDozingTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGlanceableHubTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel
 import dagger.Binds
 import dagger.Module
@@ -253,4 +255,16 @@
     abstract fun goneToGlanceableHub(
         impl: GoneToGlanceableHubTransitionViewModel
     ): DeviceEntryIconTransition
+
+    @Binds
+    @IntoSet
+    abstract fun primaryBouncerToGlanceableHub(
+        impl: PrimaryBouncerToGlanceableHubTransitionViewModel
+    ): DeviceEntryIconTransition
+
+    @Binds
+    @IntoSet
+    abstract fun dozingToGlanceableHub(
+        impl: DozingToGlanceableHubTransitionViewModel
+    ): DeviceEntryIconTransition
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
index 215ac46..1c63235 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
@@ -73,14 +73,14 @@
                         AccessibilityNodeInfoCompat.ACTION_CLICK,
                         resources.getString(R.string.accessibility_enter_hint)
                     )
+
                 override fun onInitializeAccessibilityNodeInfo(
                     v: View,
                     info: AccessibilityNodeInfo
                 ) {
                     super.onInitializeAccessibilityNodeInfo(v, info)
                     when (accessibilityHintType) {
-                        AccessibilityHintType.BOUNCER ->
-                            info.addAction(accessibilityBouncerHint)
+                        AccessibilityHintType.BOUNCER -> info.addAction(accessibilityBouncerHint)
                         AccessibilityHintType.ENTER -> info.addAction(accessibilityEnterHint)
                         AccessibilityHintType.NONE -> return
                     }
@@ -204,12 +204,12 @@
             /* reversible */ false,
         )
 
-        // LockscreenFingerprint <=> LockscreenLocked
+        // LockscreenFingerprint => LockscreenLocked
         animatedIconDrawable.addTransition(
             R.id.locked_fp,
             R.id.locked,
             context.getDrawable(R.drawable.fp_to_locked) as AnimatedVectorDrawable,
-            /* reversible */ true,
+            /* reversible */ false,
         )
 
         // LockscreenUnlocked <=> AodLocked
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt
new file mode 100644
index 0000000..aee34e1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_GLANCEABLE_HUB_DURATION
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class DozingToGlanceableHubTransitionViewModel
+@Inject
+constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition {
+    private val transitionAnimation =
+        animationFlow
+            .setup(
+                duration = TO_GLANCEABLE_HUB_DURATION,
+                edge = Edge.create(DOZING, Scenes.Communal)
+            )
+            .setupWithoutSceneContainer(edge = Edge.create(DOZING, GLANCEABLE_HUB))
+
+    override val deviceEntryParentViewAlpha: Flow<Float> =
+        transitionAnimation.immediatelyTransitionTo(1f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt
new file mode 100644
index 0000000..754fb94
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GLANCEABLE_HUB_DURATION
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class PrimaryBouncerToGlanceableHubTransitionViewModel
+@Inject
+constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition {
+    private val transitionAnimation =
+        animationFlow
+            .setup(
+                duration = TO_GLANCEABLE_HUB_DURATION,
+                edge = Edge.create(PRIMARY_BOUNCER, Scenes.Communal)
+            )
+            .setupWithoutSceneContainer(edge = Edge.create(PRIMARY_BOUNCER, GLANCEABLE_HUB))
+
+    override val deviceEntryParentViewAlpha: Flow<Float> =
+        transitionAnimation.immediatelyTransitionTo(1f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
index d0c7fbc..1f74716 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
@@ -22,6 +22,8 @@
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeAlignment
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -29,11 +31,19 @@
 
 /** Models UI state and handles user input for the Notifications Shade scene. */
 @SysUISingleton
-class NotificationsShadeSceneViewModel @Inject constructor() {
+class NotificationsShadeSceneViewModel
+@Inject
+constructor(
+    shadeInteractor: ShadeInteractor,
+) {
     val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
         MutableStateFlow(
                 mapOf(
-                    Swipe.Up to SceneFamilies.Home,
+                    if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
+                        Swipe.Up
+                    } else {
+                        Swipe.Down
+                    } to SceneFamilies.Home,
                     Back to SceneFamilies.Home,
                 )
             )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index e77bd03..4018320 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -103,8 +103,7 @@
     }
     private int mLastMaxHeight = -1;
 
-    @Override
-    public void setPageMargin(int marginPixels) {
+    public void setPageMargin(int marginPixelsStart, int marginPixelsEnd) {
         // Using page margins creates some rounding issues that interfere with the correct position
         // in the onPageChangedListener and therefore present bad positions to the PageIndicator.
         // Instead, we use negative margins in the container and positive padding in the pages,
@@ -113,14 +112,19 @@
         // QSContainerImpl resources are set onAttachedView, so this view will always have the right
         // values when attached.
         MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
-        lp.setMarginStart(-marginPixels);
-        lp.setMarginEnd(-marginPixels);
+        lp.setMarginStart(-marginPixelsStart);
+        lp.setMarginEnd(-marginPixelsEnd);
         setLayoutParams(lp);
 
         int nPages = mPages.size();
         for (int i = 0; i < nPages; i++) {
             View v = mPages.get(i);
-            v.setPadding(marginPixels, v.getPaddingTop(), marginPixels, v.getPaddingBottom());
+            v.setPadding(
+                    marginPixelsStart,
+                    v.getPaddingTop(),
+                    marginPixelsEnd,
+                    v.getPaddingBottom()
+            );
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index cc0901f..db0676e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -35,6 +35,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.qs.customize.QSCustomizer;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.shade.LargeScreenHeaderHelper;
 import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.util.LargeScreenUtils;
@@ -300,7 +301,7 @@
                 // QS panel lays out some of its content full width
                 qsPanelController.setContentMargins(mContentHorizontalPadding,
                         mContentHorizontalPadding);
-                qsPanelController.setPageMargin(mTilesPageMargin);
+                setPageMargins(qsPanelController);
             } else if (view == mHeader) {
                 quickStatusBarHeaderController.setContentMargins(mContentHorizontalPadding,
                         mContentHorizontalPadding);
@@ -318,6 +319,18 @@
         }
     }
 
+    private void setPageMargins(QSPanelController qsPanelController) {
+        if (SceneContainerFlag.isEnabled()) {
+            if (mHorizontalMargins == mTilesPageMargin * 2 + 1) {
+                qsPanelController.setPageMargin(mTilesPageMargin, mTilesPageMargin + 1);
+            } else {
+                qsPanelController.setPageMargin(mTilesPageMargin, mTilesPageMargin);
+            }
+        } else {
+            qsPanelController.setPageMargin(mTilesPageMargin, mTilesPageMargin);
+        }
+    }
+
     /**
      * Clip QS bottom using a concave shape.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index abc2b7f..032891f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -638,9 +638,9 @@
         return mListening;
     }
 
-    protected void setPageMargin(int pageMargin) {
+    protected void setPageMargin(int pageMarginStart, int pageMarginEnd) {
         if (mTileLayout instanceof PagedTileLayout) {
-            ((PagedTileLayout) mTileLayout).setPageMargin(pageMargin);
+            ((PagedTileLayout) mTileLayout).setPageMargin(pageMarginStart, pageMarginEnd);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index f76183e..d5cd8dc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -284,8 +284,8 @@
         return mView.isExpanded();
     }
 
-    void setPageMargin(int pageMargin) {
-        mView.setPageMargin(pageMargin);
+    void setPageMargin(int pageMarginStart, int pageMarginEnd) {
+        mView.setPageMargin(pageMarginStart, pageMarginEnd);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
index bd748d5..e395d30 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
@@ -26,6 +26,8 @@
 import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeAlignment
 import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -37,6 +39,7 @@
 class QuickSettingsShadeSceneViewModel
 @Inject
 constructor(
+    shadeInteractor: ShadeInteractor,
     val overlayShadeViewModel: OverlayShadeViewModel,
     val brightnessSliderViewModel: BrightnessSliderViewModel,
     val tileGridViewModel: TileGridViewModel,
@@ -46,7 +49,11 @@
     val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
         MutableStateFlow(
                 mapOf(
-                    Swipe.Up to SceneFamilies.Home,
+                    if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
+                        Swipe.Up
+                    } else {
+                        Swipe.Down
+                    } to SceneFamilies.Home,
                     Back to SceneFamilies.Home,
                 )
             )
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt
index 9e91b66..41a3c8a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
 import dagger.Binds
@@ -45,11 +46,13 @@
 constructor(
     @Application private val applicationScope: CoroutineScope,
     deviceEntryInteractor: DeviceEntryInteractor,
+    keyguardEnabledInteractor: KeyguardEnabledInteractor,
 ) : SceneResolver {
     override val targetFamily: SceneKey = SceneFamilies.Home
 
     override val resolvedScene: StateFlow<SceneKey> =
         combine(
+                keyguardEnabledInteractor.isKeyguardEnabled,
                 deviceEntryInteractor.canSwipeToEnter,
                 deviceEntryInteractor.isDeviceEntered,
                 deviceEntryInteractor.isUnlocked,
@@ -60,20 +63,23 @@
                 started = SharingStarted.Eagerly,
                 initialValue =
                     homeScene(
-                        deviceEntryInteractor.canSwipeToEnter.value,
-                        deviceEntryInteractor.isDeviceEntered.value,
-                        deviceEntryInteractor.isUnlocked.value,
+                        isKeyguardEnabled = keyguardEnabledInteractor.isKeyguardEnabled.value,
+                        canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value,
+                        isDeviceEntered = deviceEntryInteractor.isDeviceEntered.value,
+                        isUnlocked = deviceEntryInteractor.isUnlocked.value,
                     )
             )
 
     override fun includesScene(scene: SceneKey): Boolean = scene in homeScenes
 
     private fun homeScene(
+        isKeyguardEnabled: Boolean,
         canSwipeToEnter: Boolean?,
         isDeviceEntered: Boolean,
         isUnlocked: Boolean,
     ): SceneKey =
         when {
+            !isKeyguardEnabled -> Scenes.Gone
             canSwipeToEnter == true -> Scenes.Lockscreen
             !isDeviceEntered -> Scenes.Lockscreen
             !isUnlocked -> Scenes.Lockscreen
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 218853d7..fbc0748 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -22,7 +22,6 @@
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
 import com.android.internal.logging.UiEventLogger
-import com.android.internal.policy.IKeyguardStateCallback
 import com.android.systemui.CoreStartable
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -39,6 +38,7 @@
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
 import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
+import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor
 import com.android.systemui.model.SceneContainerPlugin
@@ -83,6 +83,7 @@
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterIsInstance
 import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
@@ -124,12 +125,11 @@
     private val sceneBackInteractor: SceneBackInteractor,
     private val shadeSessionStorage: SessionStorage,
     private val windowMgrLockscreenVisInteractor: WindowManagerLockscreenVisibilityInteractor,
+    private val keyguardEnabledInteractor: KeyguardEnabledInteractor,
 ) : CoreStartable {
     private val centralSurfaces: CentralSurfaces?
         get() = centralSurfacesOptLazy.get().getOrNull()
 
-    private val keyguardStateCallbacks = mutableListOf<IKeyguardStateCallback>()
-
     override fun start() {
         if (SceneContainerFlag.isEnabled) {
             sceneLogger.logFrameworkEnabled(isEnabled = true)
@@ -143,6 +143,7 @@
             hydrateWindowController()
             hydrateBackStack()
             resetShadeSessions()
+            handleKeyguardEnabledness()
         } else {
             sceneLogger.logFrameworkEnabled(
                 isEnabled = false,
@@ -653,6 +654,51 @@
         }
     }
 
+    private fun handleKeyguardEnabledness() {
+        // Automatically switches scenes when keyguard is enabled or disabled, as needed.
+        applicationScope.launch {
+            keyguardEnabledInteractor.isKeyguardEnabled
+                .sample(
+                    combine(
+                        deviceEntryInteractor.isInLockdown,
+                        deviceEntryInteractor.isDeviceEntered,
+                        ::Pair,
+                    )
+                ) { isKeyguardEnabled, (isInLockdown, isDeviceEntered) ->
+                    when {
+                        !isKeyguardEnabled && !isInLockdown && !isDeviceEntered -> {
+                            keyguardEnabledInteractor.setShowKeyguardWhenReenabled(true)
+                            Scenes.Gone to "Keyguard became disabled"
+                        }
+                        isKeyguardEnabled &&
+                            keyguardEnabledInteractor.isShowKeyguardWhenReenabled() -> {
+                            keyguardEnabledInteractor.setShowKeyguardWhenReenabled(false)
+                            Scenes.Lockscreen to "Keyguard became enabled"
+                        }
+                        else -> null
+                    }
+                }
+                .filterNotNull()
+                .collect { (targetScene, loggingReason) ->
+                    switchToScene(targetScene, loggingReason)
+                }
+        }
+
+        // Clears the showKeyguardWhenReenabled if the auth method changes to an insecure one.
+        applicationScope.launch {
+            authenticationInteractor
+                .get()
+                .authenticationMethod
+                .map { it.isSecure }
+                .distinctUntilChanged()
+                .collect { isAuthenticationMethodSecure ->
+                    if (!isAuthenticationMethodSecure) {
+                        keyguardEnabledInteractor.setShowKeyguardWhenReenabled(false)
+                    }
+                }
+        }
+    }
+
     private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) {
         sceneInteractor.changeScene(
             toScene = targetSceneKey,
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index d382b7a..a62edcb 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -25,6 +25,7 @@
 
 import android.app.Activity;
 import android.content.res.Configuration;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.view.Gravity;
@@ -32,7 +33,9 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
+import android.view.WindowInsets;
 import android.view.WindowManager;
+import android.view.WindowMetrics;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
 
@@ -152,18 +155,27 @@
 
         Configuration configuration = getResources().getConfiguration();
         int orientation = configuration.orientation;
-        int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
+        int windowWidth = getWindowAvailableWidth();
 
         if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
             boolean shouldBeFullWidth = getIntent()
                     .getBooleanExtra(EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH, false);
-            lp.width = (shouldBeFullWidth ? screenWidth : screenWidth / 2) - horizontalMargin * 2;
+            lp.width = (shouldBeFullWidth ? windowWidth : windowWidth / 2) - horizontalMargin * 2;
         } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
-            lp.width = screenWidth - horizontalMargin * 2;
+            lp.width = windowWidth - horizontalMargin * 2;
         }
 
         frame.setLayoutParams(lp);
+    }
 
+    private int getWindowAvailableWidth() {
+        final WindowMetrics metrics = getWindowManager().getCurrentWindowMetrics();
+        // Gets all excluding insets
+        final WindowInsets windowInsets = metrics.getWindowInsets();
+        Insets insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()
+                | WindowInsets.Type.displayCutout());
+        int insetsWidth = insets.right + insets.left;
+        return metrics.getBounds().width() - insetsWidth;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index d54e66e..af40cab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -4474,6 +4474,7 @@
     }
 
     void goToFullShade(long delay) {
+        SceneContainerFlag.assertInLegacyMode();
         mGoToFullShadeNeedsAnimation = true;
         mGoToFullShadeDelay = delay;
         mNeedsAnimation = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index d984685..bf53ee2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1177,6 +1177,7 @@
     }
 
     public void goToFullShade(long delay) {
+        SceneContainerFlag.assertInLegacyMode();
         mView.goToFullShade(delay);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index 361a945..16b9ab5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.accessibility;
 
-import static com.android.systemui.accessibility.Magnification.DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS;
+import static com.android.systemui.accessibility.MagnificationImpl.DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -65,7 +65,7 @@
 
 /**
  * Tests for {@link android.view.accessibility.IMagnificationConnection} retrieved from
- * {@link Magnification}
+ * {@link MagnificationImpl}
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -104,7 +104,7 @@
     private IWindowManager mIWindowManager;
 
     private IMagnificationConnection mIMagnificationConnection;
-    private Magnification mMagnification;
+    private MagnificationImpl mMagnification;
     private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
     private TestableLooper mTestableLooper;
 
@@ -119,11 +119,11 @@
                 any(IMagnificationConnection.class));
         mTestableLooper = TestableLooper.get(this);
         assertNotNull(mTestableLooper);
-        mMagnification = new Magnification(getContext(),
+        mMagnification = new MagnificationImpl(getContext(),
                 mTestableLooper.getLooper(), mContext.getMainExecutor(), mCommandQueue,
                 mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
                 mDisplayTracker, getContext().getSystemService(DisplayManager.class),
-                mA11yLogger, mIWindowManager);
+                mA11yLogger, mIWindowManager, mAccessibilityManager);
         mMagnification.mWindowMagnificationControllerSupplier =
                 new FakeWindowMagnificationControllerSupplier(
                         mContext.getSystemService(DisplayManager.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index 17b7e21..41a4116 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -84,7 +84,7 @@
     private SecureSettings mSecureSettings;
 
     private CommandQueue mCommandQueue;
-    private Magnification mMagnification;
+    private MagnificationImpl mMagnification;
     private OverviewProxyListener mOverviewProxyListener;
     private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
 
@@ -124,11 +124,12 @@
         when(mWindowMagnificationController.isActivated()).thenReturn(true);
 
         mCommandQueue = new CommandQueue(getContext(), mDisplayTracker);
-        mMagnification = new Magnification(getContext(),
+        mMagnification = new MagnificationImpl(getContext(),
                 getContext().getMainThreadHandler(), mContext.getMainExecutor(),
                 mCommandQueue, mModeSwitchesController,
                 mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker,
-                getContext().getSystemService(DisplayManager.class), mA11yLogger, mIWindowManager);
+                getContext().getSystemService(DisplayManager.class), mA11yLogger, mIWindowManager,
+                getContext().getSystemService(AccessibilityManager.class));
         mMagnification.mWindowMagnificationControllerSupplier = new FakeControllerSupplier(
                 mContext.getSystemService(DisplayManager.class), mWindowMagnificationController);
         mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index dca531a..5bae6ec 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -133,6 +133,8 @@
 
     override val topClippingBounds = MutableStateFlow<Int?>(null)
 
+    private var isShowKeyguardWhenReenabled: Boolean = false
+
     override fun setQuickSettingsVisible(isVisible: Boolean) {
         _isQuickSettingsVisible.value = isVisible
     }
@@ -268,6 +270,14 @@
     fun setIsEncryptedOrLockdown(value: Boolean) {
         _isEncryptedOrLockdown.value = value
     }
+
+    override fun setShowKeyguardWhenReenabled(isShowKeyguardWhenReenabled: Boolean) {
+        this.isShowKeyguardWhenReenabled = isShowKeyguardWhenReenabled
+    }
+
+    override fun isShowKeyguardWhenReenabled(): Boolean {
+        return isShowKeyguardWhenReenabled
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt
index 6be1939..ae33aea 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt
@@ -20,6 +20,7 @@
 
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.scene.shared.model.SceneFamilies
@@ -39,6 +40,7 @@
         HomeSceneFamilyResolver(
             applicationScope = applicationCoroutineScope,
             deviceEntryInteractor = deviceEntryInteractor,
+            keyguardEnabledInteractor = keyguardEnabledInteractor,
         )
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
index 8b887d3..a661ab6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.windowManagerLockscreenVisibilityInteractor
 import com.android.systemui.kosmos.Kosmos
@@ -71,5 +72,6 @@
         sceneBackInteractor = sceneBackInteractor,
         shadeSessionStorage = shadeSessionStorage,
         windowMgrLockscreenVisInteractor = windowManagerLockscreenVisibilityInteractor,
+        keyguardEnabledInteractor = keyguardEnabledInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index 4813794..b9918f1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -30,7 +30,7 @@
 @SysUISingleton
 class FakeShadeRepository @Inject constructor() : ShadeRepository {
     private val _qsExpansion = MutableStateFlow(0f)
-    override val qsExpansion = _qsExpansion
+    @Deprecated("Use ShadeInteractor.qsExpansion instead") override val qsExpansion = _qsExpansion
 
     private val _udfpsTransitionToFullShadeProgress = MutableStateFlow(0f)
     override val udfpsTransitionToFullShadeProgress = _udfpsTransitionToFullShadeProgress
@@ -59,12 +59,15 @@
     private val _legacyIsQsExpanded = MutableStateFlow(false)
     @Deprecated("Use ShadeInteractor instead") override val legacyIsQsExpanded = _legacyIsQsExpanded
 
+    @Deprecated("Use ShadeInteractor.isUserInteractingWithShade instead")
     override val legacyLockscreenShadeTracking = MutableStateFlow(false)
 
     private val _shadeMode = MutableStateFlow<ShadeMode>(ShadeMode.Single)
     override val shadeMode: StateFlow<ShadeMode> = _shadeMode.asStateFlow()
 
-    override val isDualShadeAlignedToBottom = false
+    private var _isDualShadeAlignedToBottom = false
+    override val isDualShadeAlignedToBottom
+        get() = _isDualShadeAlignedToBottom
 
     @Deprecated("Use ShadeInteractor instead")
     override fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean) {
@@ -139,8 +142,12 @@
         _legacyShadeExpansion.value = expandedFraction
     }
 
-    override fun setShadeMode(shadeMode: ShadeMode) {
-        _shadeMode.value = shadeMode
+    override fun setShadeMode(mode: ShadeMode) {
+        _shadeMode.value = mode
+    }
+
+    fun setDualShadeAlignedToBottom(isAlignedToBottom: Boolean) {
+        _isDualShadeAlignedToBottom = isAlignedToBottom
     }
 }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt
index 1ca3509..72a80d4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneViewModel
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 
 val Kosmos.notificationsShadeSceneViewModel: NotificationsShadeSceneViewModel by
-    Kosmos.Fixture { NotificationsShadeSceneViewModel() }
+    Kosmos.Fixture { NotificationsShadeSceneViewModel(shadeInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
index 4d81ea1..8adb26f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.qs.panels.ui.viewmodel.tileGridViewModel
 import com.android.systemui.qs.ui.adapter.qsSceneAdapter
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneViewModel
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 
 val Kosmos.quickSettingsShadeSceneViewModel: QuickSettingsShadeSceneViewModel by
     Kosmos.Fixture {
@@ -31,5 +32,6 @@
             tileGridViewModel = tileGridViewModel,
             editModeViewModel = editModeViewModel,
             qsSceneAdapter = qsSceneAdapter,
+            shadeInteractor = shadeInteractor,
         )
     }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 6bd7445..0fa5260 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -127,6 +127,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SERVICE_EXECUTING;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
 
 import android.Manifest;
 import android.annotation.IntDef;
@@ -4304,7 +4305,7 @@
                 // queued up in the app side as they're one way calls. And we'll also hold off
                 // the service timeout timer until the process is unfrozen.
                 mAm.mOomAdjuster.updateAppFreezeStateLSP(callerApp, OOM_ADJ_REASON_BIND_SERVICE,
-                        true);
+                        true, UNKNOWN_ADJ);
             }
 
             final boolean wasStopped = hostApp == null ? wasStopped(s) : false;
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 211f952..6433f2c 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -83,6 +83,8 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.ServiceThread;
 
+import dalvik.annotation.optimization.NeverCompile;
+
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -98,8 +100,6 @@
 import java.util.Random;
 import java.util.Set;
 
-import dalvik.annotation.optimization.NeverCompile;
-
 public final class CachedAppOptimizer {
 
     // Flags stored in the DeviceConfig API.
@@ -248,7 +248,7 @@
     private static final int COMPACT_ACTION_ANON_FLAG = 2;
 
     private static final String ATRACE_COMPACTION_TRACK = "Compaction";
-    private static final String ATRACE_FREEZER_TRACK = "Freezer";
+    public static final String ATRACE_FREEZER_TRACK = "Freezer";
 
     private static final int FREEZE_BINDER_TIMEOUT_MS = 0;
     private static final int FREEZE_DEADLOCK_TIMEOUT_MS = 1000;
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index c9e0666..27c194b 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1832,7 +1832,8 @@
             state.setNoKillOnBgRestrictedAndIdle(false);
             // If this UID is currently allowlisted, it should not be frozen.
             final UidRecord uidRec = app.getUidRecord();
-            app.mOptRecord.setShouldNotFreeze(uidRec != null && uidRec.isCurAllowListed());
+            app.mOptRecord.setShouldNotFreeze(uidRec != null && uidRec.isCurAllowListed(),
+                    ProcessCachedOptimizerRecord.SHOULD_NOT_FREEZE_REASON_UID_ALLOWLISTED, mAdjSeq);
         }
 
         final int appUid = app.info.uid;
@@ -2672,7 +2673,9 @@
 
         if (client.mOptRecord.shouldNotFreeze()) {
             // Propagate the shouldNotFreeze flag down the bindings.
-            if (app.mOptRecord.setShouldNotFreeze(true, dryRun)) {
+            if (app.mOptRecord.setShouldNotFreeze(true, dryRun,
+                    app.mOptRecord.shouldNotFreezeReason()
+                    | client.mOptRecord.shouldNotFreezeReason(), mAdjSeq)) {
                 // Bail out early, as we only care about the return value for a dryrun.
                 return true;
             }
@@ -2739,7 +2742,10 @@
             if (cr.hasFlag(Context.BIND_ALLOW_OOM_MANAGEMENT)) {
                 // Similar to BIND_WAIVE_PRIORITY, keep it unfrozen.
                 if (clientAdj < CACHED_APP_MIN_ADJ) {
-                    if (app.mOptRecord.setShouldNotFreeze(true, dryRun)) {
+                    if (app.mOptRecord.setShouldNotFreeze(true, dryRun,
+                            app.mOptRecord.shouldNotFreezeReason()
+                            | ProcessCachedOptimizerRecord
+                            .SHOULD_NOT_FREEZE_REASON_BINDER_ALLOW_OOM_MANAGEMENT, mAdjSeq)) {
                         // Bail out early, as we only care about the return value for a dryrun.
                         return true;
                     }
@@ -2976,7 +2982,10 @@
             // bound by an unfrozen app via a WPRI binding has to remain
             // unfrozen.
             if (clientAdj < CACHED_APP_MIN_ADJ) {
-                if (app.mOptRecord.setShouldNotFreeze(true, dryRun)) {
+                if (app.mOptRecord.setShouldNotFreeze(true, dryRun,
+                        app.mOptRecord.shouldNotFreezeReason()
+                        | ProcessCachedOptimizerRecord
+                        .SHOULD_NOT_FREEZE_REASON_BIND_WAIVE_PRIORITY, mAdjSeq)) {
                     // Bail out early, as we only care about the return value for a dryrun.
                     return true;
                 }
@@ -3117,7 +3126,9 @@
         }
         if (client.mOptRecord.shouldNotFreeze()) {
             // Propagate the shouldNotFreeze flag down the bindings.
-            if (app.mOptRecord.setShouldNotFreeze(true, dryRun)) {
+            if (app.mOptRecord.setShouldNotFreeze(true, dryRun,
+                    app.mOptRecord.shouldNotFreezeReason()
+                    | client.mOptRecord.shouldNotFreezeReason(), mAdjSeq)) {
                 // Bail out early, as we only care about the return value for a dryrun.
                 return true;
             }
@@ -3476,7 +3487,7 @@
             changes |= ActivityManagerService.ProcessChangeItem.CHANGE_ACTIVITIES;
         }
 
-        updateAppFreezeStateLSP(app, oomAdjReson, false);
+        updateAppFreezeStateLSP(app, oomAdjReson, false, oldOomAdj);
 
         if (state.getReportedProcState() != state.getCurProcState()) {
             state.setReportedProcState(state.getCurProcState());
@@ -3873,16 +3884,37 @@
 
     @GuardedBy({"mService", "mProcLock"})
     void updateAppFreezeStateLSP(ProcessRecord app, @OomAdjReason int oomAdjReason,
-            boolean immediate) {
+            boolean immediate, int oldOomAdj) {
         if (!mCachedAppOptimizer.useFreezer()) {
             return;
         }
 
+        final ProcessCachedOptimizerRecord opt = app.mOptRecord;
+        final ProcessStateRecord state = app.mState;
+        if (Flags.traceUpdateAppFreezeStateLsp()) {
+            final boolean oomAdjChanged =
+                    (state.getCurAdj() >= FREEZER_CUTOFF_ADJ ^ oldOomAdj >= FREEZER_CUTOFF_ADJ)
+                    || oldOomAdj == UNKNOWN_ADJ;
+            final boolean shouldNotFreezeChanged = opt.shouldNotFreezeAdjSeq() == mAdjSeq;
+            if ((oomAdjChanged || shouldNotFreezeChanged)
+                    && Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+                Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                        CachedAppOptimizer.ATRACE_FREEZER_TRACK,
+                        "updateAppFreezeStateLSP " + app.processName
+                        + " isFreezeExempt: " + opt.isFreezeExempt()
+                        + " isFrozen: " + opt.isFrozen()
+                        + " shouldNotFreeze: " + opt.shouldNotFreeze()
+                        + " shouldNotFreezeReason: " + opt.shouldNotFreezeReason()
+                        + " curAdj: " + state.getCurAdj()
+                        + " oldOomAdj: " + oldOomAdj
+                        + " immediate: " + immediate);
+            }
+        }
+
         if (app.mOptRecord.isFreezeExempt()) {
             return;
         }
 
-        final ProcessCachedOptimizerRecord opt = app.mOptRecord;
         // if an app is already frozen and shouldNotFreeze becomes true, immediately unfreeze
         if (opt.isFrozen() && opt.shouldNotFreeze()) {
             mCachedAppOptimizer.unfreezeAppLSP(app,
@@ -3890,7 +3922,6 @@
             return;
         }
 
-        final ProcessStateRecord state = app.mState;
         // Use current adjustment when freezing, set adjustment when unfreezing.
         if (state.getCurAdj() >= FREEZER_CUTOFF_ADJ && !opt.isFrozen()
                 && !opt.shouldNotFreeze()) {
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index ea92571..53643b8 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import android.annotation.IntDef;
 import android.annotation.UptimeMillisLong;
 import android.app.ActivityManagerInternal.OomAdjReason;
 import android.util.TimeUtils;
@@ -23,14 +24,31 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.io.PrintWriter;
-
 import dalvik.annotation.optimization.NeverCompile;
 
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * The state info of app when it's cached, used by the optimizer.
  */
 final class ProcessCachedOptimizerRecord {
+
+    static final int SHOULD_NOT_FREEZE_REASON_NONE = 1;
+    static final int SHOULD_NOT_FREEZE_REASON_UID_ALLOWLISTED = 1 << 1;
+    static final int SHOULD_NOT_FREEZE_REASON_BINDER_ALLOW_OOM_MANAGEMENT = 1 << 2;
+    static final int SHOULD_NOT_FREEZE_REASON_BIND_WAIVE_PRIORITY = 1 << 3;
+
+    @IntDef(flag = true, prefix = {"SHOULD_NOT_FREEZE_REASON_"}, value = {
+        SHOULD_NOT_FREEZE_REASON_NONE,
+        SHOULD_NOT_FREEZE_REASON_UID_ALLOWLISTED,
+        SHOULD_NOT_FREEZE_REASON_BINDER_ALLOW_OOM_MANAGEMENT,
+        SHOULD_NOT_FREEZE_REASON_BIND_WAIVE_PRIORITY,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ShouldNotFreezeReason {}
+
     private final ProcessRecord mApp;
 
     private final ActivityManagerGlobalLock mProcLock;
@@ -112,6 +130,18 @@
     private boolean mShouldNotFreeze;
 
     /**
+     * Reason for mShouldNotFreeze being set to a particular value.
+     */
+    @GuardedBy("mProcLock")
+    private @ShouldNotFreezeReason int mShouldNotFreezeReason;
+
+    /**
+     * The value of adjSeq when last time mShouldNotFreeze was set.
+     */
+    @GuardedBy("mProcLock")
+    private int mShouldNotFreezeAdjSeq;
+
+    /**
      * Exempt from freezer (now for system apps with INSTALL_PACKAGES permission)
      */
     @GuardedBy("mProcLock")
@@ -275,8 +305,19 @@
     }
 
     @GuardedBy("mProcLock")
-    void setShouldNotFreeze(boolean shouldNotFreeze) {
-        setShouldNotFreeze(shouldNotFreeze, false);
+    @ShouldNotFreezeReason int shouldNotFreezeReason() {
+        return mShouldNotFreezeReason;
+    }
+
+    @GuardedBy("mProcLock")
+    int shouldNotFreezeAdjSeq() {
+        return mShouldNotFreezeAdjSeq;
+    }
+
+    @GuardedBy("mProcLock")
+    void setShouldNotFreeze(boolean shouldNotFreeze, @ShouldNotFreezeReason int reason,
+            int adjSeq) {
+        setShouldNotFreeze(shouldNotFreeze, false, reason, adjSeq);
     }
 
     /**
@@ -284,10 +325,23 @@
      * if it was a real run.
      */
     @GuardedBy("mProcLock")
-    boolean setShouldNotFreeze(boolean shouldNotFreeze, boolean dryRun) {
+    boolean setShouldNotFreeze(boolean shouldNotFreeze, boolean dryRun,
+            @ShouldNotFreezeReason int reason, int adjSeq) {
         if (dryRun) {
             return mFrozen && !shouldNotFreeze;
         }
+        if (Flags.traceUpdateAppFreezeStateLsp()) {
+            if (shouldNotFreeze) {
+                reason &= ~SHOULD_NOT_FREEZE_REASON_NONE;
+            } else {
+                reason = SHOULD_NOT_FREEZE_REASON_NONE;
+            }
+
+            if (reason != mShouldNotFreezeReason || shouldNotFreeze != mShouldNotFreeze) {
+                mShouldNotFreezeAdjSeq = adjSeq;
+            }
+            mShouldNotFreezeReason = reason;
+        }
         mShouldNotFreeze = shouldNotFreeze;
         return false;
     }
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index a30590f..0dfa330 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -125,6 +125,14 @@
 }
 
 flag {
+    name: "trace_update_app_freeze_state_lsp"
+    namespace: "backstage_power"
+    description: "Output process oom adj and other metadata to trace in updateAppFreezeStateLSP"
+    bug: "345547733"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "simplify_process_traversal"
     namespace: "backstage_power"
     description: "Simplify the OomAdjuster's process traversal mechanism."
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6d1983e..c89992d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -11698,7 +11698,7 @@
     static final int LOG_NB_EVENTS_VOLUME = 100;
     static final int LOG_NB_EVENTS_DYN_POLICY = 10;
     static final int LOG_NB_EVENTS_SPATIAL = 30;
-    static final int LOG_NB_EVENTS_SOUND_DOSE = 30;
+    static final int LOG_NB_EVENTS_SOUND_DOSE = 50;
 
     static final int LOG_NB_EVENTS_LOUDNESS_CODEC = 30;
 
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 749044e..8ea28be 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -577,6 +577,7 @@
         static final int DOSE_REPEAT_5X = 2;
         static final int DOSE_ACCUMULATION_START = 3;
         static final int LOWER_VOLUME_TO_RS1 = 4;
+        static final int UPDATE_ABS_VOLUME_ATTENUATION = 5;
 
         final int mEventType;
         final float mFloatValue;
@@ -608,6 +609,10 @@
             return new SoundDoseEvent(LOWER_VOLUME_TO_RS1, 0 /*ignored*/, 0 /*ignored*/);
         }
 
+        static SoundDoseEvent getAbsVolumeAttenuationEvent(float attenuation, int device) {
+            return new SoundDoseEvent(UPDATE_ABS_VOLUME_ATTENUATION, attenuation, device);
+        }
+
         @Override
         public String eventToString() {
             switch (mEventType) {
@@ -623,6 +628,10 @@
                     return "CSD accumulating: RS2 entered";
                 case LOWER_VOLUME_TO_RS1:
                     return "CSD lowering volume to RS1";
+                case UPDATE_ABS_VOLUME_ATTENUATION:
+                    return String.format(java.util.Locale.US,
+                            "Updating CSD absolute volume attenuation on device %d with %.2f dB ",
+                            mLongValue, mFloatValue);
             }
             return new StringBuilder("FIXME invalid event type:").append(mEventType).toString();
         }
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index e28ae95..5c74304 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -39,6 +39,7 @@
 import android.media.ISoundDose;
 import android.media.ISoundDoseCallback;
 import android.media.SoundDoseRecord;
+import android.media.VolumeInfo;
 import android.os.Binder;
 import android.os.Message;
 import android.os.RemoteException;
@@ -895,6 +896,8 @@
 
         try {
             if (!isAbsoluteVolume) {
+                mLogger.enqueue(
+                        SoundDoseEvent.getAbsVolumeAttenuationEvent(/*attenuation=*/0.f, device));
                 // remove any possible previous attenuation
                 soundDose.updateAttenuation(/* attenuationDB= */0.f, device);
 
@@ -903,10 +906,11 @@
 
             if (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC
                     && safeDevicesContains(device)) {
-                soundDose.updateAttenuation(
-                        -AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC,
-                                (newIndex + 5) / 10,
-                                device), device);
+                float attenuationDb = -AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC,
+                        (newIndex + 5) / 10, device);
+                mLogger.enqueue(
+                        SoundDoseEvent.getAbsVolumeAttenuationEvent(attenuationDb, device));
+                soundDose.updateAttenuation(attenuationDb, device);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Could not apply the attenuation for MEL calculation with volume index "
@@ -1313,22 +1317,30 @@
 
     /** Called when handling MSG_LOWER_VOLUME_TO_RS1 */
     private void onLowerVolumeToRs1() {
-        mLogger.enqueue(SoundDoseEvent.getLowerVolumeToRs1Event());
         final ArrayList<AudioDeviceAttributes> devices = mAudioService.getDevicesForAttributesInt(
-                new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(), true);
-        final int nativeDeviceType;
-        final AudioDeviceAttributes ada;
-        if (!devices.isEmpty()) {
-            ada = devices.get(0);
-            nativeDeviceType = ada.getInternalType();
-        } else {
-            nativeDeviceType = AudioSystem.DEVICE_OUT_USB_HEADSET;
-            ada = new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_USB_HEADSET, "");
+                new AudioAttributes.Builder().setUsage(
+                        AudioAttributes.USAGE_MEDIA).build(), /*forVolume=*/true);
+        if (devices.isEmpty()) {
+            Log.e(TAG, "Cannot lower the volume to RS1, no devices registered for USAGE_MEDIA");
+            return;
         }
-        final int index = safeMediaVolumeIndex(nativeDeviceType);
-        mAudioService.setStreamVolumeWithAttributionInt(STREAM_MUSIC, index / 10, /*flags*/ 0, ada,
-                mContext.getOpPackageName(), /*attributionTag=*/null,
-                true /*canChangeMuteAndUpdateController*/);
+        final AudioDeviceAttributes ada = devices.get(0);
+        final int nativeDeviceType = ada.getInternalType();
+        final int index = safeMediaVolumeIndex(nativeDeviceType) / 10;
+        final VolumeInfo curVolume = mAudioService.getDeviceVolume(
+                new VolumeInfo.Builder(STREAM_MUSIC).build(), ada,
+                /*callingPackage=*/"sounddosehelper");
+
+        if (index < curVolume.getVolumeIndex()) {
+            mLogger.enqueue(SoundDoseEvent.getLowerVolumeToRs1Event());
+            mAudioService.setStreamVolumeWithAttributionInt(STREAM_MUSIC, index, /*flags*/ 0, ada,
+                    mContext.getOpPackageName(), /*attributionTag=*/null,
+                    /*canChangeMuteAndUpdateController=*/true);
+        } else {
+            Log.i(TAG, "The current volume " + curVolume.getVolumeIndex()
+                    + " for device type " + nativeDeviceType
+                    + " is already smaller or equal to the safe index volume " + index);
+        }
     }
 
     // StreamVolumeCommand contains the information needed to defer the process of
diff --git a/services/core/java/com/android/server/display/BrightnessSetting.java b/services/core/java/com/android/server/display/BrightnessSetting.java
index 651828b..7d26004 100644
--- a/services/core/java/com/android/server/display/BrightnessSetting.java
+++ b/services/core/java/com/android/server/display/BrightnessSetting.java
@@ -131,6 +131,25 @@
     }
 
     /**
+     * Sets the brightness. Does not send update event to listeners.
+     * @param brightness The value to which the brightness is to be set.
+     */
+    public void setBrightnessNoNotify(float brightness) {
+        if (Float.isNaN(brightness)) {
+            Slog.w(TAG, "Attempting to init invalid brightness");
+            return;
+        }
+        synchronized (mSyncRoot) {
+            if (brightness != mBrightness) {
+                mPersistentDataStore.setBrightness(mLogicalDisplay.getPrimaryDisplayDeviceLocked(),
+                        brightness, mUserSerial
+                );
+            }
+            mBrightness = brightness;
+        }
+    }
+
+    /**
      * @return The brightness for the default display in nits. Used when the underlying display
      * device has changed but we want to persist the nit value.
      */
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index c298bbf..de9715a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1567,7 +1567,9 @@
             // even if they range changes what it means in absolute terms.
             mDisplayBrightnessController.updateScreenBrightnessSetting(
                     MathUtils.constrain(unthrottledBrightnessState,
-                            clampedState.getMinBrightness(), clampedState.getMaxBrightness()));
+                            clampedState.getMinBrightness(), clampedState.getMaxBrightness()),
+                    Math.min(mBrightnessRangeController.getCurrentBrightnessMax(),
+                            clampedState.getMaxBrightness()));
         }
 
         // The current brightness to use has been calculated at this point, and HbmController should
@@ -2471,12 +2473,20 @@
 
     @Override
     public void setBrightness(float brightness) {
-        mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness));
+        // After HBMController and NBMController migration to Clampers framework
+        // currentBrightnessMax should be taken from clampers controller
+        // TODO(b/263362199)
+        mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness),
+                mBrightnessRangeController.getCurrentBrightnessMax());
     }
 
     @Override
     public void setBrightness(float brightness, int userSerial) {
-        mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness), userSerial);
+        // After HBMController and NBMController migration to Clampers framework
+        // currentBrightnessMax should be taken from clampers controller
+        // TODO(b/263362199)
+        mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness), userSerial,
+                mBrightnessRangeController.getCurrentBrightnessMax());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 4982a0b0..c632e77 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -313,13 +313,18 @@
     /**
      * Notifies the brightnessSetting to persist the supplied brightness value.
      */
-    public void setBrightness(float brightnessValue) {
+    public void setBrightness(float brightnessValue, float maxBrightness) {
         // Update the setting, which will eventually call back into DPC to have us actually
         // update the display with the new value.
         mBrightnessSetting.setBrightness(brightnessValue);
         if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
             float nits = convertToNits(brightnessValue);
-            if (nits >= 0) {
+            float currentlyStoredNits = mBrightnessSetting.getBrightnessNitsForDefaultDisplay();
+            // Don't override settings if the brightness is set to max, but the currently
+            // stored value is greater. On multi-screen device, when switching between a
+            // screen with a wider brightness range and one with a narrower brightness range,
+            // the stored value shouldn't change.
+            if (nits >= 0 && !(brightnessValue == maxBrightness && currentlyStoredNits > nits)) {
                 mBrightnessSetting.setBrightnessNitsForDefaultDisplay(nits);
             }
         }
@@ -328,15 +333,15 @@
     /**
      * Notifies the brightnessSetting to persist the supplied brightness value for a user.
      */
-    public void setBrightness(float brightnessValue, int userSerial) {
+    public void setBrightness(float brightnessValue, int userSerial, float maxBrightness) {
         mBrightnessSetting.setUserSerial(userSerial);
-        setBrightness(brightnessValue);
+        setBrightness(brightnessValue, maxBrightness);
     }
 
     /**
      * Sets the current screen brightness, and notifies the BrightnessSetting about the change.
      */
-    public void updateScreenBrightnessSetting(float brightnessValue) {
+    public void updateScreenBrightnessSetting(float brightnessValue, float maxBrightness) {
         synchronized (mLock) {
             if (!BrightnessUtils.isValidBrightnessValue(brightnessValue)
                     || brightnessValue == mCurrentScreenBrightness) {
@@ -345,7 +350,7 @@
             setCurrentScreenBrightnessLocked(brightnessValue);
         }
         notifyCurrentScreenBrightness();
-        setBrightness(brightnessValue);
+        setBrightness(brightnessValue, maxBrightness);
     }
 
     /**
@@ -582,7 +587,7 @@
                 float brightnessForDefaultDisplay = getBrightnessFromNits(
                         brightnessNitsForDefaultDisplay);
                 if (BrightnessUtils.isValidBrightnessValue(brightnessForDefaultDisplay)) {
-                    mBrightnessSetting.setBrightness(brightnessForDefaultDisplay);
+                    mBrightnessSetting.setBrightnessNoNotify(brightnessForDefaultDisplay);
                     currentBrightnessSetting = brightnessForDefaultDisplay;
                 }
             }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 440343d..0e628b7 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -377,11 +377,6 @@
     @SharedByAllUsersField
     private final SparseArray<String> mVirtualDeviceMethodMap = new SparseArray<>();
 
-    // TODO: Instantiate mSwitchingController for each user.
-    @NonNull
-    @MultiUserUnawareField
-    private InputMethodSubtypeSwitchingController mSwitchingController;
-
     @Nullable
     private StatusBarManagerInternal mStatusBarManagerInternal;
     @SharedByAllUsersField
@@ -1287,7 +1282,7 @@
             @SuppressWarnings("GuardedBy") final IntFunction<InputMethodBindingController>
                     bindingControllerFactory = userId -> new InputMethodBindingController(userId,
                     InputMethodManagerService.this);
-            mUserDataRepository = new UserDataRepository(mHandler, mUserManagerInternal,
+            mUserDataRepository = new UserDataRepository(mContext, mHandler, mUserManagerInternal,
                     bindingControllerForTesting != null ? bindingControllerForTesting
                             : bindingControllerFactory);
             for (int id : mUserManagerInternal.getUserIds()) {
@@ -1295,9 +1290,9 @@
             }
 
             final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
-
-            mSwitchingController = new InputMethodSubtypeSwitchingController(context, settings);
-            getUserData(mCurrentUserId).mHardwareKeyboardShortcutController.update(settings);
+            final var userData = getUserData(mCurrentUserId);
+            userData.mSwitchingController.resetCircularListLocked(settings);
+            userData.mHardwareKeyboardShortcutController.update(settings);
             mMenuController = new InputMethodMenuController(this);
             mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
             mVisibilityApplier = new DefaultImeVisibilityApplier(this);
@@ -2928,7 +2923,6 @@
      *         {@link PackageManager#COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED} is not updated.
      *     </li>
      *     <li>{@link InputMethodBindingController#getDeviceIdToShowIme()} is ignored.</li>
-     *     <li>{@link #mSwitchingController} is ignored.</li>
      *     <li>{@link #mPreventImeStartupUnlessTextEditor} is ignored.</li>
      *     <li>and so on.</li>
      * </ul>
@@ -2963,6 +2957,7 @@
         }
 
         final var userData = getUserData(userId);
+        userData.mSwitchingController.resetCircularListLocked(settings);
         userData.mHardwareKeyboardShortcutController.update(settings);
     }
 
@@ -3039,13 +3034,9 @@
             resetCurrentMethodAndClientLocked(UnbindReason.NO_IME);
         }
 
-        // TODO: Instantiate mSwitchingController for each user.
-        if (userId == mSwitchingController.getUserId()) {
-            mSwitchingController.resetCircularListLocked(settings);
-        } else {
-            mSwitchingController = new InputMethodSubtypeSwitchingController(mContext, settings);
-        }
-        getUserData(userId).mHardwareKeyboardShortcutController.update(settings);
+        final var userData = getUserData(userId);
+        userData.mSwitchingController.resetCircularListLocked(settings);
+        userData.mHardwareKeyboardShortcutController.update(settings);
         sendOnNavButtonFlagsChangedLocked();
     }
 
@@ -4188,8 +4179,9 @@
         final int userId = mCurrentUserId;
         final var bindingController = getInputMethodBindingController(userId);
         final var currentImi = bindingController.getSelectedMethod();
-        final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
-                onlyCurrentIme, currentImi, bindingController.getCurrentSubtype());
+        final ImeSubtypeListItem nextSubtype = getUserData(userId).mSwitchingController
+                .getNextInputMethodLocked(onlyCurrentIme, currentImi,
+                        bindingController.getCurrentSubtype());
         if (nextSubtype == null) {
             return false;
         }
@@ -4207,8 +4199,9 @@
             final int userId = mCurrentUserId;
             final var bindingController = getInputMethodBindingController(userId);
             final var currentImi = bindingController.getSelectedMethod();
-            final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
-                    false /* onlyCurrentIme */, currentImi, bindingController.getCurrentSubtype());
+            final ImeSubtypeListItem nextSubtype = getUserData(userId).mSwitchingController
+                    .getNextInputMethodLocked(false /* onlyCurrentIme */, currentImi,
+                            bindingController.getCurrentSubtype());
             return nextSubtype != null;
         }
     }
@@ -4641,13 +4634,11 @@
                 return;
             }
             final int userId = mCurrentUserId;
-            if (userId != mSwitchingController.getUserId()) {
-                return;
-            }
-            final var imi = getInputMethodBindingController(userId).getSelectedMethod();
+            final var bindingController = getInputMethodBindingController(userId);
+            final InputMethodInfo imi = bindingController.getSelectedMethod();
             if (imi != null) {
-                mSwitchingController.onUserActionLocked(imi,
-                        getInputMethodBindingController(userId).getCurrentSubtype());
+                getUserData(userId).mSwitchingController.onUserActionLocked(imi,
+                        bindingController.getCurrentSubtype());
             }
         }
     }
@@ -5308,13 +5299,9 @@
 
         updateDefaultVoiceImeIfNeededLocked();
 
-        // TODO: Instantiate mSwitchingController for each user.
-        if (userId == mSwitchingController.getUserId()) {
-            mSwitchingController.resetCircularListLocked(settings);
-        } else {
-            mSwitchingController = new InputMethodSubtypeSwitchingController(mContext, settings);
-        }
-        getUserData(userId).mHardwareKeyboardShortcutController.update(settings);
+        final var userData = getUserData(userId);
+        userData.mSwitchingController.resetCircularListLocked(settings);
+        userData.mHardwareKeyboardShortcutController.update(settings);
 
         sendOnNavButtonFlagsChangedLocked();
 
@@ -6115,6 +6102,8 @@
                         p.println("      hasMainConnection="
                                 + u.mBindingController.hasMainConnection());
                         p.println("      isVisibleBound=" + u.mBindingController.isVisibleBound());
+                        p.println("      mSwitchingController:");
+                        u.mSwitchingController.dump(p, "        ");
                     };
             mUserDataRepository.forAllUserData(userDataDump);
 
@@ -6135,8 +6124,6 @@
             p.println("  mSettingsObserver=" + mSettingsObserver);
             p.println("  mStylusIds=" + (mStylusIds != null
                     ? Arrays.toString(mStylusIds.toArray()) : ""));
-            p.println("  mSwitchingController:");
-            mSwitchingController.dump(p, "    ");
 
             p.println("  mStartInputHistory:");
             mStartInputHistory.dump(pw, "    ");
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 5da4e89..1f089bf 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.content.Context;
 import android.content.pm.UserInfo;
 import android.os.Handler;
 import android.util.SparseArray;
@@ -35,12 +36,15 @@
 
     private final IntFunction<InputMethodBindingController> mBindingControllerFactory;
 
+    @NonNull
+    private final Context mContext;
+
     @GuardedBy("ImfLock.class")
     @NonNull
     UserData getOrCreate(@UserIdInt int userId) {
         UserData userData = mUserData.get(userId);
         if (userData == null) {
-            userData = new UserData(userId, mBindingControllerFactory.apply(userId));
+            userData = new UserData(userId, mBindingControllerFactory.apply(userId), mContext);
             mUserData.put(userId, userData);
         }
         return userData;
@@ -53,8 +57,11 @@
         }
     }
 
-    UserDataRepository(@NonNull Handler handler, @NonNull UserManagerInternal userManagerInternal,
+    UserDataRepository(
+            @NonNull Context context,
+            @NonNull Handler handler, @NonNull UserManagerInternal userManagerInternal,
             @NonNull IntFunction<InputMethodBindingController> bindingControllerFactory) {
+        mContext = context;
         mBindingControllerFactory = bindingControllerFactory;
         userManagerInternal.addUserLifecycleListener(
                 new UserManagerInternal.UserLifecycleListener() {
@@ -89,17 +96,23 @@
         final InputMethodBindingController mBindingController;
 
         @NonNull
+        final InputMethodSubtypeSwitchingController mSwitchingController;
+
+        @NonNull
         final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController;
 
         /**
          * Intended to be instantiated only from this file.
          */
         private UserData(@UserIdInt int userId,
-                @NonNull InputMethodBindingController bindingController) {
+                @NonNull InputMethodBindingController bindingController, @NonNull Context context) {
             mUserId = userId;
             mBindingController = bindingController;
+            final var emptySettings = InputMethodSettings.createEmptyMap(userId);
+            mSwitchingController = new InputMethodSubtypeSwitchingController(context,
+                    emptySettings);
             mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
-                    InputMethodSettings.createEmptyMap(userId));
+                    emptySettings);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 286e789..1938150 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -68,7 +68,6 @@
 import android.location.LocationRequest;
 import android.location.LocationResult;
 import android.location.LocationResult.BadLocationException;
-import android.location.flags.Flags;
 import android.location.provider.ProviderProperties;
 import android.location.provider.ProviderRequest;
 import android.location.util.identity.CallerIdentity;
@@ -1050,22 +1049,10 @@
                 stopBatching();
 
                 if (mStarted && mGnssNative.getCapabilities().hasScheduling()) {
-                    if (Flags.gnssCallStopBeforeSetPositionMode()) {
-                        GnssPositionMode positionMode = new GnssPositionMode(mPositionMode,
-                                GNSS_POSITION_RECURRENCE_PERIODIC, mFixInterval,
-                                /* preferredAccuracy= */ 0,
-                                /* preferredTime= */ 0,
-                                mProviderRequest.isLowPower());
-                        if (!positionMode.equals(mLastPositionMode)) {
-                            stopNavigating();
-                            startNavigating();
-                        }
-                    } else {
-                        // change period and/or lowPowerMode
-                        if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC,
-                                mFixInterval, mProviderRequest.isLowPower())) {
-                            Log.e(TAG, "set_position_mode failed in updateRequirements");
-                        }
+                    // change period and/or lowPowerMode
+                    if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC,
+                            mFixInterval, mProviderRequest.isLowPower())) {
+                        Log.e(TAG, "set_position_mode failed in updateRequirements");
                     }
                 } else if (!mStarted) {
                     // start GPS
@@ -1248,32 +1235,11 @@
             }
 
             int interval = mGnssNative.getCapabilities().hasScheduling() ? mFixInterval : 1000;
-
-            if (Flags.gnssCallStopBeforeSetPositionMode()) {
-                boolean success = mGnssNative.setPositionMode(mPositionMode,
-                        GNSS_POSITION_RECURRENCE_PERIODIC, interval,
-                        /* preferredAccuracy= */ 0,
-                        /* preferredTime= */ 0,
-                        mProviderRequest.isLowPower());
-                if (success) {
-                    mLastPositionMode = new GnssPositionMode(mPositionMode,
-                            GNSS_POSITION_RECURRENCE_PERIODIC, interval,
-                            /* preferredAccuracy= */ 0,
-                            /* preferredTime= */ 0,
-                            mProviderRequest.isLowPower());
-                } else {
-                    mLastPositionMode = null;
-                    setStarted(false);
-                    Log.e(TAG, "set_position_mode failed in startNavigating()");
-                    return;
-                }
-            } else {
-                if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC,
-                        interval, mProviderRequest.isLowPower())) {
-                    setStarted(false);
-                    Log.e(TAG, "set_position_mode failed in startNavigating()");
-                    return;
-                }
+            if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC,
+                    interval, mProviderRequest.isLowPower())) {
+                setStarted(false);
+                Log.e(TAG, "set_position_mode failed in startNavigating()");
+                return;
             }
             if (!mGnssNative.start()) {
                 setStarted(false);
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 11b9e77..f85b8cc 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -17,6 +17,7 @@
 package com.android.server.power;
 
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
@@ -197,9 +198,10 @@
             FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector,
             Executor backgroundExecutor, PowerManagerFlags powerManagerFlags, Injector injector) {
         mContext = context;
+        mInjector = (injector == null) ? new RealInjector() : injector;
         mFlags = powerManagerFlags;
         mBatteryStats = batteryStats;
-        mAppOps = mContext.getSystemService(AppOpsManager.class);
+        mAppOps = mInjector.getAppOpsManager(context);
         mSuspendBlocker = suspendBlocker;
         mPolicy = policy;
         mFaceDownDetector = faceDownDetector;
@@ -230,7 +232,6 @@
         mShowWirelessChargingAnimationConfig = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim);
 
-        mInjector = (injector == null) ? new RealInjector() : injector;
         mWakeLockLog = mInjector.getWakeLockLog(context);
         // Initialize interactive state for battery stats.
         try {
@@ -264,6 +265,7 @@
     /**
      * Called when a wake lock is acquired.
      */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public void onWakeLockAcquired(int flags, String tag, String packageName,
             int ownerUid, int ownerPid, WorkSource workSource, String historyTag,
             IWakeLockCallback callback) {
@@ -273,27 +275,28 @@
                     + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
                     + ", workSource=" + workSource);
         }
-        notifyWakeLockListener(callback, tag, true, ownerUid, flags);
-        final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
-        if (monitorType >= 0) {
-            try {
-                final boolean unimportantForLogging = ownerUid == Process.SYSTEM_UID
-                        && (flags & PowerManager.UNIMPORTANT_FOR_LOGGING) != 0;
-                if (workSource != null) {
-                    mBatteryStats.noteStartWakelockFromSource(workSource, ownerPid, tag,
-                            historyTag, monitorType, unimportantForLogging);
-                } else {
-                    mBatteryStats.noteStartWakelock(ownerUid, ownerPid, tag, historyTag,
-                            monitorType, unimportantForLogging);
-                    // XXX need to deal with disabled operations.
-                    mAppOps.startOpNoThrow(AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName);
-                }
-            } catch (RemoteException ex) {
-                // Ignore
-            }
-        }
-
+        notifyWakeLockListener(callback, tag, true, ownerUid, ownerPid, flags, workSource,
+                packageName, historyTag);
         if (!mFlags.improveWakelockLatency()) {
+            final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
+            if (monitorType >= 0) {
+                try {
+                    final boolean unimportantForLogging = ownerUid == Process.SYSTEM_UID
+                            && (flags & PowerManager.UNIMPORTANT_FOR_LOGGING) != 0;
+                    if (workSource != null) {
+                        mBatteryStats.noteStartWakelockFromSource(workSource, ownerPid, tag,
+                                historyTag, monitorType, unimportantForLogging);
+                    } else {
+                        mBatteryStats.noteStartWakelock(ownerUid, ownerPid, tag, historyTag,
+                                monitorType, unimportantForLogging);
+                        // XXX need to deal with disabled operations.
+                        mAppOps.startOpNoThrow(AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName,
+                                false, null, null);
+                    }
+                } catch (RemoteException ex) {
+                    // Ignore
+                }
+            }
             mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags, /*eventTime=*/ -1);
         }
         mWakefulnessSessionObserver.onWakeLockAcquired(flags);
@@ -404,6 +407,7 @@
     /**
      * Called when a wake lock is released.
      */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public void onWakeLockReleased(int flags, String tag, String packageName,
             int ownerUid, int ownerPid, WorkSource workSource, String historyTag,
             IWakeLockCallback callback, int releaseReason) {
@@ -413,23 +417,24 @@
                     + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
                     + ", workSource=" + workSource);
         }
-        notifyWakeLockListener(callback, tag, false, ownerUid, flags);
-        final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
-        if (monitorType >= 0) {
-            try {
-                if (workSource != null) {
-                    mBatteryStats.noteStopWakelockFromSource(workSource, ownerPid, tag,
-                            historyTag, monitorType);
-                } else {
-                    mBatteryStats.noteStopWakelock(ownerUid, ownerPid, tag,
-                            historyTag, monitorType);
-                    mAppOps.finishOp(AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName);
-                }
-            } catch (RemoteException ex) {
-                // Ignore
-            }
-        }
+        notifyWakeLockListener(callback, tag, false, ownerUid, ownerPid, flags, workSource,
+                packageName, historyTag);
         if (!mFlags.improveWakelockLatency()) {
+            final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
+            if (monitorType >= 0) {
+                try {
+                    if (workSource != null) {
+                        mBatteryStats.noteStopWakelockFromSource(workSource, ownerPid, tag,
+                                historyTag, monitorType);
+                    } else {
+                        mBatteryStats.noteStopWakelock(ownerUid, ownerPid, tag,
+                                historyTag, monitorType);
+                        mAppOps.finishOp(AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName, null);
+                    }
+                } catch (RemoteException ex) {
+                    // Ignore
+                }
+            }
             mWakeLockLog.onWakeLockReleased(tag, ownerUid, /*eventTime=*/ -1);
         }
         mWakefulnessSessionObserver.onWakeLockReleased(flags, releaseReason);
@@ -1049,24 +1054,75 @@
     }
 
     private void notifyWakeLockListener(IWakeLockCallback callback, String tag, boolean isEnabled,
-            int ownerUid, int flags) {
-        if (callback != null) {
-            long currentTime = mInjector.currentTimeMillis();
-            mHandler.post(() -> {
+            int ownerUid, int ownerPid, int flags, WorkSource workSource, String packageName,
+            String historyTag) {
+        mHandler.post(() -> {
+            if (mFlags.improveWakelockLatency()) {
+                long currentTime = mInjector.currentTimeMillis();
+                if (isEnabled) {
+                    notifyWakelockAcquisition(tag, ownerUid, ownerPid, flags,
+                            workSource, packageName, historyTag, currentTime);
+                } else {
+                    notifyWakelockRelease(tag, ownerUid, ownerPid, flags,
+                            workSource, packageName, historyTag, currentTime);
+                }
+            }
+
+            if (callback != null) {
                 try {
-                    if (mFlags.improveWakelockLatency()) {
-                        if (isEnabled) {
-                            mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags, currentTime);
-                        } else {
-                            mWakeLockLog.onWakeLockReleased(tag, ownerUid, currentTime);
-                        }
-                    }
                     callback.onStateChanged(isEnabled);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Wakelock.mCallback [" + tag + "] is already dead.", e);
                 }
-            });
+            }
+        });
+    }
+
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    private void notifyWakelockAcquisition(String tag, int ownerUid, int ownerPid, int flags,
+            WorkSource workSource, String packageName, String historyTag, long currentTime) {
+        final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
+        if (monitorType >= 0) {
+            try {
+                final boolean unimportantForLogging = ownerUid == Process.SYSTEM_UID
+                        && (flags & PowerManager.UNIMPORTANT_FOR_LOGGING) != 0;
+                if (workSource != null) {
+                    mBatteryStats.noteStartWakelockFromSource(workSource, ownerPid, tag,
+                            historyTag, monitorType, unimportantForLogging);
+                } else {
+                    mBatteryStats.noteStartWakelock(ownerUid, ownerPid, tag, historyTag,
+                            monitorType, unimportantForLogging);
+                    // XXX need to deal with disabled operations.
+                    mAppOps.startOpNoThrow(
+                            AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName,
+                            false, null, null);
+                }
+            } catch (RemoteException ex) {
+                // Do Nothing
+            }
         }
+        mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags, currentTime);
+    }
+
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    private void notifyWakelockRelease(String tag, int ownerUid, int ownerPid, int flags,
+            WorkSource workSource, String packageName, String historyTag, long currentTime) {
+        final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
+        if (monitorType >= 0) {
+            try {
+                if (workSource != null) {
+                    mBatteryStats.noteStopWakelockFromSource(workSource, ownerPid, tag,
+                            historyTag, monitorType);
+                } else {
+                    mBatteryStats.noteStopWakelock(ownerUid, ownerPid, tag,
+                            historyTag, monitorType);
+                    mAppOps.finishOp(AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName, null);
+                }
+            } catch (RemoteException ex) {
+                // Ignore
+            }
+        }
+        mWakeLockLog.onWakeLockReleased(tag, ownerUid, currentTime);
     }
 
     private final class NotifierHandler extends Handler {
@@ -1114,6 +1170,11 @@
          * Gets the WakeLockLog object
          */
         WakeLockLog getWakeLockLog(Context context);
+
+        /**
+         * Gets the AppOpsManager system service
+         */
+        AppOpsManager getAppOpsManager(Context context);
     }
 
     static class RealInjector implements Injector {
@@ -1126,5 +1187,10 @@
         public WakeLockLog getWakeLockLog(Context context) {
             return new WakeLockLog(context);
         }
+
+        @Override
+        public AppOpsManager getAppOpsManager(Context context) {
+            return context.getSystemService(AppOpsManager.class);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index e9c08e0..717c399 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1649,7 +1649,8 @@
                 // activity, so this isn't just deliver-to-top
                 && mMovedToTopActivity == null
                 && !transitionController.hasOrderChanges()
-                && !transitionController.isTransientHide(startedActivityRootTask)) {
+                && !transitionController.isTransientHide(startedActivityRootTask)
+                && !newTransition.hasChanged(mLastStartActivityRecord)) {
             // We just delivered to top, so there isn't an actual transition here.
             if (!forceTransientTransition) {
                 newTransition.abort();
@@ -1792,6 +1793,7 @@
                     activity.destroyIfPossible("Removes redundant singleInstance");
                 }
             }
+            targetTaskTop.mTransitionController.collect(targetTaskTop);
             recordTransientLaunchIfNeeded(targetTaskTop);
             // Recycle the target task for this launch.
             startResult =
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index b6e6991..cd5576f 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -952,7 +952,7 @@
                 // Schedule transaction.
                 if (shouldDispatchLaunchActivityItemIndependently(r.info.packageName, r.getUid())) {
                     // LaunchActivityItem has @UnsupportedAppUsage usages.
-                    // Guard the bundleClientTransactionFlag feature with targetSDK on Android 15+.
+                    // Guard with targetSDK on Android 15+.
                     // To not bundle the transaction, dispatch the pending before schedule new
                     // transaction.
                     mService.getLifecycleManager().dispatchPendingTransaction(proc.getThread());
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index e396f2b..bf34759 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -35,7 +35,6 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.window.flags.Flags;
 
 /**
  * Class that is able to combine multiple client lifecycle transition requests and/or callbacks,
@@ -72,9 +71,8 @@
      * @throws RemoteException
      *
      * @see ClientTransaction
-     * @deprecated use {@link #scheduleTransactionItem(IApplicationThread, ClientTransactionItem)}.
      */
-    @Deprecated
+    @VisibleForTesting
     void scheduleTransaction(@NonNull ClientTransaction transaction) throws RemoteException {
         final IApplicationThread client = transaction.getClient();
         try {
@@ -114,22 +112,13 @@
      */
     void scheduleTransactionItem(@NonNull IApplicationThread client,
             @NonNull ClientTransactionItem transactionItem) throws RemoteException {
-        // The behavior is different depending on the flag.
-        // When flag is on, we wait until RootWindowContainer#performSurfacePlacementNoTrace to
-        // dispatch all pending transactions at once.
-        if (Flags.bundleClientTransactionFlag()) {
-            final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client);
-            clientTransaction.addTransactionItem(transactionItem);
+        // Wait until RootWindowContainer#performSurfacePlacementNoTrace to dispatch all pending
+        // transactions at once.
+        final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client);
+        clientTransaction.addTransactionItem(transactionItem);
 
-            onClientTransactionItemScheduled(clientTransaction,
-                    false /* shouldDispatchImmediately */);
-        } else {
-            // TODO(b/260873529): cleanup after launch.
-            final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
-            clientTransaction.addTransactionItem(transactionItem);
-
-            scheduleTransaction(clientTransaction);
-        }
+        onClientTransactionItemScheduled(clientTransaction,
+                false /* shouldDispatchImmediately */);
     }
 
     void scheduleTransactionAndLifecycleItems(@NonNull IApplicationThread client,
@@ -156,27 +145,18 @@
             @NonNull ClientTransactionItem transactionItem,
             @NonNull ActivityLifecycleItem lifecycleItem,
             boolean shouldDispatchImmediately) throws RemoteException {
-        // The behavior is different depending on the flag.
-        // When flag is on, we wait until RootWindowContainer#performSurfacePlacementNoTrace to
-        // dispatch all pending transactions at once.
-        if (Flags.bundleClientTransactionFlag()) {
-            final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client);
-            clientTransaction.addTransactionItem(transactionItem);
-            clientTransaction.addTransactionItem(lifecycleItem);
+        // Wait until RootWindowContainer#performSurfacePlacementNoTrace to dispatch all pending
+        // transactions at once.
+        final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client);
+        clientTransaction.addTransactionItem(transactionItem);
+        clientTransaction.addTransactionItem(lifecycleItem);
 
-            onClientTransactionItemScheduled(clientTransaction, shouldDispatchImmediately);
-        } else {
-            // TODO(b/260873529): cleanup after launch.
-            final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
-            clientTransaction.addTransactionItem(transactionItem);
-            clientTransaction.addTransactionItem(lifecycleItem);
-            scheduleTransaction(clientTransaction);
-        }
+        onClientTransactionItemScheduled(clientTransaction, shouldDispatchImmediately);
     }
 
     /** Executes all the pending transactions. */
     void dispatchPendingTransactions() {
-        if (!Flags.bundleClientTransactionFlag() || mPendingTransactions.isEmpty()) {
+        if (mPendingTransactions.isEmpty()) {
             return;
         }
         Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "clientTransactionsDispatched");
@@ -196,9 +176,6 @@
 
     /** Executes the pending transaction for the given client process. */
     void dispatchPendingTransaction(@NonNull IApplicationThread client) {
-        if (!Flags.bundleClientTransactionFlag()) {
-            return;
-        }
         final ClientTransaction pendingTransaction = mPendingTransactions.remove(client.asBinder());
         if (pendingTransaction != null) {
             try {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 54ba47e..6abd488 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -152,7 +152,6 @@
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.utils.Slogf;
-import com.android.window.flags.Flags;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -789,13 +788,12 @@
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
 
-        if (Flags.bundleClientTransactionFlag()) {
-            // mWmService.mResizingWindows is populated in #applySurfaceChangesTransaction()
-            handleResizingWindows();
+        // mWmService.mResizingWindows is populated in #applySurfaceChangesTransaction()
+        handleResizingWindows();
+        clearFrameChangingWindows();
 
-            // Called after #handleResizingWindows to include WindowStateResizeItem if any.
-            mWmService.mAtmService.getLifecycleManager().dispatchPendingTransactions();
-        }
+        // Called after #handleResizingWindows to include WindowStateResizeItem if any.
+        mWmService.mAtmService.getLifecycleManager().dispatchPendingTransactions();
 
         // Send any pending task-info changes that were queued-up during a layout deferment
         mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
@@ -839,11 +837,6 @@
             }
         }
 
-        if (!Flags.bundleClientTransactionFlag()) {
-            handleResizingWindows();
-        }
-        clearFrameChangingWindows();
-
         if (mWmService.mDisplayFrozen) {
             ProtoLog.v(WM_DEBUG_ORIENTATION,
                     "With display frozen, orientationChangeComplete=%b",
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index acdb66a..3cd071b 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -82,7 +82,6 @@
 import android.app.ResultInfo;
 import android.app.WindowConfiguration;
 import android.app.servertransaction.ActivityResultItem;
-import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.NewIntentItem;
 import android.app.servertransaction.PauseActivityItem;
 import android.app.servertransaction.ResumeActivityItem;
@@ -1604,9 +1603,6 @@
 
             try {
                 final IApplicationThread appThread = next.app.getThread();
-                final ClientTransaction transaction = Flags.bundleClientTransactionFlag()
-                        ? null
-                        : ClientTransaction.obtain(appThread);
                 // Deliver all pending results.
                 final ArrayList<ResultInfo> a = next.results;
                 if (a != null) {
@@ -1617,24 +1613,16 @@
                         }
                         final ActivityResultItem activityResultItem = ActivityResultItem.obtain(
                                 next.token, a);
-                        if (transaction == null) {
-                            mAtmService.getLifecycleManager().scheduleTransactionItem(
-                                    appThread, activityResultItem);
-                        } else {
-                            transaction.addTransactionItem(activityResultItem);
-                        }
+                        mAtmService.getLifecycleManager().scheduleTransactionItem(
+                                appThread, activityResultItem);
                     }
                 }
 
                 if (next.newIntents != null) {
                     final NewIntentItem newIntentItem = NewIntentItem.obtain(
                             next.token, next.newIntents, true /* resume */);
-                    if (transaction == null) {
-                        mAtmService.getLifecycleManager().scheduleTransactionItem(
-                                appThread, newIntentItem);
-                    } else {
-                        transaction.addTransactionItem(newIntentItem);
-                    }
+                    mAtmService.getLifecycleManager().scheduleTransactionItem(
+                            appThread, newIntentItem);
                 }
 
                 // Well the app will no longer be stopped.
@@ -1651,13 +1639,8 @@
                 final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(
                         next.token, topProcessState, dc.isNextTransitionForward(),
                         next.shouldSendCompatFakeFocus());
-                if (transaction == null) {
-                    mAtmService.getLifecycleManager().scheduleTransactionItem(
-                            appThread, resumeActivityItem);
-                } else {
-                    transaction.addTransactionItem(resumeActivityItem);
-                    mAtmService.getLifecycleManager().scheduleTransaction(transaction);
-                }
+                mAtmService.getLifecycleManager().scheduleTransactionItem(
+                        appThread, resumeActivityItem);
 
                 ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Resumed %s", next);
             } catch (Exception e) {
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index c4e932a..26315f9 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -171,11 +171,7 @@
 
         TaskFragmentOrganizerState(@NonNull ITaskFragmentOrganizer organizer, int pid, int uid,
                 boolean isSystemOrganizer) {
-            if (Flags.bundleClientTransactionFlag()) {
-                mAppThread = getAppThread(pid, uid);
-            } else {
-                mAppThread = null;
-            }
+            mAppThread = getAppThread(pid, uid);
             mOrganizer = organizer;
             mOrganizerPid = pid;
             mOrganizerUid = uid;
@@ -431,13 +427,9 @@
                 return;
             }
             try {
-                if (Flags.bundleClientTransactionFlag()) {
-                    // Dispatch through IApplicationThread to ensure the binder call is in order
-                    // with ClientTransaction.
-                    mAppThread.scheduleTaskFragmentTransaction(mOrganizer, transaction);
-                } else {
-                    mOrganizer.onTransactionReady(transaction);
-                }
+                // Dispatch through IApplicationThread to ensure the binder call is in order
+                // with ClientTransaction.
+                mAppThread.scheduleTaskFragmentTransaction(mOrganizer, transaction);
             } catch (RemoteException e) {
                 Slog.d(TAG, "Exception sending TaskFragmentTransaction", e);
                 return;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 2572128..33b604c 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -3231,6 +3231,12 @@
         Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, cookie);
     }
 
+    boolean hasChanged(WindowContainer wc) {
+        final ChangeInfo chg = mChanges.get(wc);
+        if (chg == null) return false;
+        return chg.hasChanged();
+    }
+
     @VisibleForTesting
     static class ChangeInfo {
         private static final int FLAG_NONE = 0;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 9d4a3b8..b08135f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3760,31 +3760,13 @@
         final boolean isDragResizing = isDragResizing();
 
         markRedrawForSyncReported();
-
-        if (Flags.bundleClientTransactionFlag()) {
-            getProcess().scheduleClientTransactionItem(
-                    WindowStateResizeItem.obtain(mClient, mLastReportedFrames, reportDraw,
-                            mLastReportedConfiguration, mLastReportedInsetsState, forceRelayout,
-                            alwaysConsumeSystemBars, displayId,
-                            syncWithBuffers ? mSyncSeqId : -1, isDragResizing,
-                            mLastReportedActivityWindowInfo));
-            onResizePostDispatched(drawPending, prevRotation, displayId);
-        } else {
-            // TODO(b/301870955): cleanup after launch
-            try {
-                mClient.resized(mLastReportedFrames, reportDraw, mLastReportedConfiguration,
-                        mLastReportedInsetsState, forceRelayout, alwaysConsumeSystemBars, displayId,
+        getProcess().scheduleClientTransactionItem(
+                WindowStateResizeItem.obtain(mClient, mLastReportedFrames, reportDraw,
+                        mLastReportedConfiguration, mLastReportedInsetsState, forceRelayout,
+                        alwaysConsumeSystemBars, displayId,
                         syncWithBuffers ? mSyncSeqId : -1, isDragResizing,
-                        mLastReportedActivityWindowInfo);
-                onResizePostDispatched(drawPending, prevRotation, displayId);
-            } catch (RemoteException e) {
-                // Cancel orientation change of this window to avoid blocking unfreeze display.
-                setOrientationChanging(false);
-                mLastFreezeDuration = (int) (SystemClock.elapsedRealtime()
-                        - mWmService.mDisplayFreezeTime);
-                Slog.w(TAG, "Failed to report 'resized' to " + this + " due to " + e);
-            }
-        }
+                        mLastReportedActivityWindowInfo));
+        onResizePostDispatched(drawPending, prevRotation, displayId);
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 42bd75a..2dfb303 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -181,6 +181,7 @@
         // InputMethodManagerService.
         doNothing().when(mContext).enforceCallingPermission(anyString(), anyString());
         doNothing().when(mContext).sendBroadcastAsUser(any(), any());
+        doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt());
         doReturn(null).when(mContext).registerReceiver(any(), any());
         doReturn(null)
                 .when(mContext)
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
index 79943f6..7592d34 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
@@ -18,16 +18,22 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.content.Context;
 import android.content.pm.UserInfo;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.Looper;
 import android.platform.test.ravenwood.RavenwoodRule;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+
 import com.android.server.pm.UserManagerInternal;
 
 import org.junit.After;
@@ -61,10 +67,15 @@
 
     private IntFunction<InputMethodBindingController> mBindingControllerFactory;
 
+    private Context mMockedContext;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         SecureSettingsWrapper.startTestMode();
+        mMockedContext = spy(InstrumentationRegistry.getInstrumentation().getTargetContext());
+        doReturn(mMockedContext).when(mMockedContext).createContextAsUser(any(), anyInt());
+
         mHandler = new Handler(Looper.getMainLooper());
         mBindingControllerFactory = new IntFunction<InputMethodBindingController>() {
 
@@ -85,8 +96,8 @@
         // Create UserDataRepository and capture the user lifecycle listener
         final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
         final var bindingControllerFactorySpy = spy(mBindingControllerFactory);
-        final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal,
-                bindingControllerFactorySpy);
+        final var repository = new UserDataRepository(mMockedContext, mHandler,
+                mMockUserManagerInternal, bindingControllerFactorySpy);
 
         verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
         final var listener = captor.getValue();
@@ -112,7 +123,8 @@
     public void testUserDataRepository_removesUserInfoOnUserRemovedEvent() {
         // Create UserDataRepository and capture the user lifecycle listener
         final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
-        final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal,
+        final var repository = new UserDataRepository(mMockedContext, mHandler,
+                mMockUserManagerInternal,
                 userId -> new InputMethodBindingController(userId, mMockInputMethodManagerService));
 
         verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
@@ -134,8 +146,8 @@
 
     @Test
     public void testGetOrCreate() {
-        final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal,
-                mBindingControllerFactory);
+        final var repository = new UserDataRepository(mMockedContext, mHandler,
+                mMockUserManagerInternal, mBindingControllerFactory);
 
         synchronized (ImfLock.class) {
             final var userData = repository.getOrCreate(ANY_USER_ID);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index 88d3238..d7936fe 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
@@ -255,7 +256,8 @@
     @Test
     public void setBrightnessSetsInBrightnessSetting() {
         float brightnessValue = 0.3f;
-        mDisplayBrightnessController.setBrightness(brightnessValue);
+        float maxBrightnessValue = 0.65f;
+        mDisplayBrightnessController.setBrightness(brightnessValue, maxBrightnessValue);
         verify(mBrightnessSetting).setBrightness(brightnessValue);
     }
 
@@ -266,20 +268,24 @@
 
         // Sets the appropriate value when valid, and not equal to the current brightness
         float brightnessValue = 0.3f;
-        mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue);
+        float maxBrightnessValue = 0.65f;
+        mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue,
+                maxBrightnessValue);
         assertEquals(mDisplayBrightnessController.getCurrentBrightness(), brightnessValue, 0.0f);
         verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable);
         verify(mBrightnessSetting).setBrightness(brightnessValue);
 
         // Does nothing if the value is invalid
-        mDisplayBrightnessController.updateScreenBrightnessSetting(Float.NaN);
+        clearInvocations(mBrightnessSetting);
+        mDisplayBrightnessController.updateScreenBrightnessSetting(Float.NaN, maxBrightnessValue);
         verifyNoMoreInteractions(mBrightnessChangeExecutor, mBrightnessSetting);
 
         // Does nothing if the value is same as the current brightness
         brightnessValue = 0.2f;
         mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(brightnessValue);
         verify(mBrightnessChangeExecutor, times(2)).execute(mOnBrightnessChangeRunnable);
-        mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue);
+        mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue,
+                maxBrightnessValue);
         verifyNoMoreInteractions(mBrightnessChangeExecutor, mBrightnessSetting);
     }
 
@@ -366,7 +372,7 @@
         when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
         mDisplayBrightnessController.setAutomaticBrightnessController(
                 automaticBrightnessController);
-        verify(mBrightnessSetting).setBrightness(brightness);
+        verify(mBrightnessSetting).setBrightnessNoNotify(brightness);
         assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f);
         clearInvocations(automaticBrightnessController, mBrightnessSetting);
 
@@ -378,7 +384,7 @@
         when(mBrightnessSetting.getBrightness()).thenReturn(brightness);
         mDisplayBrightnessController.setAutomaticBrightnessController(
                 automaticBrightnessController);
-        verify(mBrightnessSetting, never()).setBrightness(brightness);
+        verify(mBrightnessSetting, never()).setBrightnessNoNotify(brightness);
         assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f);
         clearInvocations(automaticBrightnessController, mBrightnessSetting);
 
@@ -395,15 +401,73 @@
         assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f);
         verifyZeroInteractions(automaticBrightnessController);
         verify(mBrightnessSetting, never()).getBrightnessNitsForDefaultDisplay();
-        verify(mBrightnessSetting, never()).setBrightness(brightness);
+        verify(mBrightnessSetting, never()).setBrightnessNoNotify(brightness);
     }
 
     @Test
+    public void testDoesNotSetBrightnessNits_settingMaxBrightnessAndStoredValueGreater() {
+        float brightnessValue = 0.65f;
+        float maxBrightness = 0.65f;
+        float nits = 200f;
+        float storedNits = 300f;
+
+        when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(storedNits);
+        AutomaticBrightnessController automaticBrightnessController = mock(
+                AutomaticBrightnessController.class);
+        when(automaticBrightnessController.convertToNits(brightnessValue)).thenReturn(nits);
+        mDisplayBrightnessController.mAutomaticBrightnessController = automaticBrightnessController;
+
+        mDisplayBrightnessController.setBrightness(brightnessValue, maxBrightness);
+
+        verify(mBrightnessSetting, never()).setBrightnessNitsForDefaultDisplay(anyFloat());
+    }
+
+    @Test
+    public void testSetsBrightnessNits_storedValueLower() {
+        float brightnessValue = 0.65f;
+        float maxBrightness = 0.65f;
+        float nits = 200f;
+        float storedNits = 100f;
+
+        when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(storedNits);
+        AutomaticBrightnessController automaticBrightnessController = mock(
+                AutomaticBrightnessController.class);
+        when(automaticBrightnessController.convertToNits(brightnessValue)).thenReturn(nits);
+        mDisplayBrightnessController.mAutomaticBrightnessController = automaticBrightnessController;
+
+        mDisplayBrightnessController.setBrightness(brightnessValue, maxBrightness);
+
+        verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits);
+    }
+
+    @Test
+    public void testSetsBrightnessNits_lowerThanMax() {
+        float brightnessValue = 0.60f;
+        float maxBrightness = 0.65f;
+        float nits = 200f;
+        float storedNits = 300f;
+
+        when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(storedNits);
+        AutomaticBrightnessController automaticBrightnessController = mock(
+                AutomaticBrightnessController.class);
+        when(automaticBrightnessController.convertToNits(brightnessValue)).thenReturn(nits);
+        mDisplayBrightnessController.mAutomaticBrightnessController = automaticBrightnessController;
+
+        mDisplayBrightnessController.setBrightness(brightnessValue, maxBrightness);
+
+        verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits);
+    }
+
+
+    @Test
     public void testChangeBrightnessNitsWhenUserChanges() {
         float brightnessValue1 = 0.3f;
         float nits1 = 200f;
+        int userSerial1 = 1;
         float brightnessValue2 = 0.5f;
         float nits2 = 300f;
+        int userSerial2 = 2;
+        float maxBrightness = 0.65f;
         AutomaticBrightnessController automaticBrightnessController = mock(
                 AutomaticBrightnessController.class);
         when(automaticBrightnessController.convertToNits(brightnessValue1)).thenReturn(nits1);
@@ -417,13 +481,13 @@
         verify(automaticBrightnessStrategy)
                 .setAutomaticBrightnessController(automaticBrightnessController);
 
-        mDisplayBrightnessController.setBrightness(brightnessValue1, 1 /* user-serial */);
-        verify(mBrightnessSetting).setUserSerial(1);
+        mDisplayBrightnessController.setBrightness(brightnessValue1, userSerial1, maxBrightness);
+        verify(mBrightnessSetting).setUserSerial(userSerial1);
         verify(mBrightnessSetting).setBrightness(brightnessValue1);
         verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits1);
 
-        mDisplayBrightnessController.setBrightness(brightnessValue2, 2 /* user-serial */);
-        verify(mBrightnessSetting).setUserSerial(2);
+        mDisplayBrightnessController.setBrightness(brightnessValue2, userSerial2, maxBrightness);
+        verify(mBrightnessSetting).setUserSerial(userSerial2);
         verify(mBrightnessSetting).setBrightness(brightnessValue2);
         verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits2);
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssLocationProviderTest.java
deleted file mode 100644
index c5e6824..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssLocationProviderTest.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.location.gnss;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.app.AlarmManager;
-import android.app.AppOpsManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.location.GnssCapabilities;
-import android.location.LocationManager;
-import android.location.LocationManagerInternal;
-import android.location.flags.Flags;
-import android.location.provider.ProviderRequest;
-import android.os.PowerManager;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.flag.junit.SetFlagsRule;
-import android.provider.Settings;
-import android.telephony.TelephonyManager;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.modules.utils.testing.ExtendedMockitoRule;
-import com.android.server.LocalServices;
-import com.android.server.location.gnss.hal.FakeGnssHal;
-import com.android.server.location.gnss.hal.GnssNative;
-import com.android.server.location.injector.Injector;
-import com.android.server.location.injector.TestInjector;
-import com.android.server.timedetector.TimeDetectorInternal;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.quality.Strictness;
-
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.Set;
-
-@Presubmit
-@androidx.test.filters.SmallTest
-@RunWith(AndroidJUnit4.class)
-public class GnssLocationProviderTest {
-
-    @Rule
-    public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
-            .setStrictness(Strictness.WARN)
-            .mockStatic(Settings.Global.class)
-            .build();
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-    private @Mock Context mContext;
-    private @Mock LocationManagerInternal mLocationManagerInternal;
-    private @Mock LocationManager mLocationManager;
-    private @Mock TimeDetectorInternal mTimeDetectorInternal;
-    private @Mock GnssConfiguration mMockConfiguration;
-    private @Mock GnssMetrics mGnssMetrics;
-    private @Mock PowerManager mPowerManager;
-    private @Mock TelephonyManager mTelephonyManager;
-    private @Mock AppOpsManager mAppOpsManager;
-    private @Mock AlarmManager mAlarmManager;
-    private @Mock PowerManager.WakeLock mWakeLock;
-    private @Mock ContentResolver mContentResolver;
-    private @Mock UserManager mUserManager;
-    private @Mock UserHandle mUserHandle;
-    private Set<UserHandle> mUserHandleSet = new HashSet<>();
-
-    private GnssNative mGnssNative;
-
-    private GnssLocationProvider mTestProvider;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        doReturn("mypackage").when(mContext).getPackageName();
-        doReturn("attribution").when(mContext).getAttributionTag();
-        doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
-        doReturn(mPowerManager).when(mContext).getSystemService("power");
-        doReturn(mAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
-        doReturn(mTelephonyManager).when(mContext).getSystemService(Context.TELEPHONY_SERVICE);
-        doReturn(mAlarmManager).when(mContext).getSystemService(Context.ALARM_SERVICE);
-        doReturn(mLocationManager).when(mContext).getSystemService(LocationManager.class);
-        doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
-        mUserHandleSet.add(mUserHandle);
-        doReturn(true).when(mLocationManager).isLocationEnabledForUser(eq(mUserHandle));
-        doReturn(mUserHandleSet).when(mUserManager).getVisibleUsers();
-        doReturn(mContentResolver).when(mContext).getContentResolver();
-        doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
-        LocalServices.addService(LocationManagerInternal.class, mLocationManagerInternal);
-        LocalServices.addService(TimeDetectorInternal.class, mTimeDetectorInternal);
-        FakeGnssHal fakeGnssHal = new FakeGnssHal();
-        GnssNative.setGnssHalForTest(fakeGnssHal);
-        Injector injector = new TestInjector(mContext);
-        mGnssNative = spy(Objects.requireNonNull(
-                GnssNative.create(injector, mMockConfiguration)));
-        doReturn(true).when(mGnssNative).init();
-        GnssCapabilities gnssCapabilities = new GnssCapabilities.Builder().setHasScheduling(
-                true).build();
-        doReturn(gnssCapabilities).when(mGnssNative).getCapabilities();
-
-        mTestProvider = new GnssLocationProvider(mContext, mGnssNative, mGnssMetrics);
-        mGnssNative.register();
-    }
-
-    @After
-    public void tearDown() {
-        LocalServices.removeServiceForTest(LocationManagerInternal.class);
-        LocalServices.removeServiceForTest(TimeDetectorInternal.class);
-    }
-
-    @Test
-    public void testStartNavigating() {
-        ProviderRequest providerRequest = new ProviderRequest.Builder().setIntervalMillis(
-                0).build();
-
-        mTestProvider.onSetRequest(providerRequest);
-        verify(mGnssNative).start();
-    }
-
-    @Test
-    public void testUpdateRequirements_sameRequest() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_GNSS_CALL_STOP_BEFORE_SET_POSITION_MODE);
-        ProviderRequest providerRequest = new ProviderRequest.Builder().setIntervalMillis(
-                0).build();
-
-        mTestProvider.onSetRequest(providerRequest);
-        verify(mGnssNative).start();
-
-        // set the same request
-        mTestProvider.onSetRequest(providerRequest);
-        verify(mGnssNative, never()).stop();
-        verify(mGnssNative, times(1)).start();
-    }
-
-    @Test
-    public void testUpdateRequirements_differentRequest() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_GNSS_CALL_STOP_BEFORE_SET_POSITION_MODE);
-        ProviderRequest providerRequest = new ProviderRequest.Builder().setIntervalMillis(
-                0).build();
-
-        mTestProvider.onSetRequest(providerRequest);
-        verify(mGnssNative).start();
-
-        // set a different request
-        providerRequest = new ProviderRequest.Builder().setIntervalMillis(2000).build();
-        mTestProvider.onSetRequest(providerRequest);
-        verify(mGnssNative).stop();
-        verify(mGnssNative, times(2)).start();
-    }
-}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
index 4460c6a..ce2bb95 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
@@ -31,6 +31,7 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.hardware.SensorManager;
@@ -41,7 +42,6 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.VibrationAttributes;
 import android.os.Vibrator;
 import android.os.test.TestLooper;
@@ -82,8 +82,12 @@
     @Mock private StatusBarManagerInternal mStatusBarManagerInternal;
     @Mock private WakeLockLog mWakeLockLog;
 
+    @Mock private IBatteryStats mBatteryStats;
+
     @Mock private PowerManagerFlags mPowerManagerFlags;
 
+    @Mock private AppOpsManager mAppOpsManager;
+
     private PowerManagerService mService;
     private Context mContextSpy;
     private Resources mResourcesSpy;
@@ -230,7 +234,7 @@
     public void testOnWakeLockListener_RemoteException_NoRethrow() {
         when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true);
         createNotifier();
-
+        clearInvocations(mWakeLockLog, mBatteryStats, mAppOpsManager);
         IWakeLockCallback exceptingCallback = new IWakeLockCallback.Stub() {
             @Override public void onStateChanged(boolean enabled) throws RemoteException {
                 throw new RemoteException("Just testing");
@@ -245,6 +249,7 @@
         verifyZeroInteractions(mWakeLockLog);
         mTestLooper.dispatchAll();
         verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, 1);
+
         mNotifier.onWakeLockAcquired(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
                 "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
                 exceptingCallback);
@@ -277,6 +282,115 @@
         verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, -1);
     }
 
+
+    @Test
+    public void testOnWakeLockListener_FullWakeLock_ProcessesOnHandler() throws RemoteException {
+        when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true);
+        createNotifier();
+
+        IWakeLockCallback exceptingCallback = new IWakeLockCallback.Stub() {
+            @Override public void onStateChanged(boolean enabled) throws RemoteException {
+                throw new RemoteException("Just testing");
+            }
+        };
+        clearInvocations(mWakeLockLog, mBatteryStats, mAppOpsManager);
+
+        final int uid = 1234;
+        final int pid = 5678;
+
+        // Release the wakelock
+        mNotifier.onWakeLockReleased(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
+                "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
+                exceptingCallback);
+
+        // No interaction because we expect that to happen in async
+        verifyZeroInteractions(mWakeLockLog, mBatteryStats, mAppOpsManager);
+
+        // Progressing the looper, and validating all the interactions
+        mTestLooper.dispatchAll();
+        verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, 1);
+        verify(mBatteryStats).noteStopWakelock(uid, pid, "wakelockTag", /* historyTag= */ null,
+                BatteryStats.WAKE_TYPE_FULL);
+        verify(mAppOpsManager).finishOp(AppOpsManager.OP_WAKE_LOCK, uid,
+                "my.package.name", null);
+
+        clearInvocations(mWakeLockLog, mBatteryStats, mAppOpsManager);
+
+        // Acquire the wakelock
+        mNotifier.onWakeLockAcquired(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
+                "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
+                exceptingCallback);
+
+        // No interaction because we expect that to happen in async
+        verifyNoMoreInteractions(mWakeLockLog, mBatteryStats, mAppOpsManager);
+
+        // Progressing the looper, and validating all the interactions
+        mTestLooper.dispatchAll();
+        verify(mWakeLockLog).onWakeLockAcquired("wakelockTag", uid,
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK, 1);
+        verify(mBatteryStats).noteStartWakelock(uid, pid, "wakelockTag", /* historyTag= */ null,
+                BatteryStats.WAKE_TYPE_FULL, false);
+        verify(mAppOpsManager).startOpNoThrow(AppOpsManager.OP_WAKE_LOCK, uid,
+                "my.package.name", false, null, null);
+
+        // Test with improveWakelockLatency flag false, hence the wakelock log will run on the same
+        // thread
+        clearInvocations(mWakeLockLog, mBatteryStats, mAppOpsManager);
+        when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(false);
+
+        mNotifier.onWakeLockAcquired(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
+                "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
+                exceptingCallback);
+        verify(mWakeLockLog).onWakeLockAcquired("wakelockTag", uid,
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK, -1);
+
+        mNotifier.onWakeLockReleased(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
+                "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
+                exceptingCallback);
+        verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, -1);
+    }
+
+    @Test
+    public void testOnWakeLockListener_FullWakeLock_ProcessesInSync() throws RemoteException {
+        createNotifier();
+
+        IWakeLockCallback exceptingCallback = new IWakeLockCallback.Stub() {
+            @Override public void onStateChanged(boolean enabled) throws RemoteException {
+                throw new RemoteException("Just testing");
+            }
+        };
+        clearInvocations(mWakeLockLog, mBatteryStats, mAppOpsManager);
+
+        final int uid = 1234;
+        final int pid = 5678;
+
+        // Release the wakelock
+        mNotifier.onWakeLockReleased(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
+                "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
+                exceptingCallback);
+
+        verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, -1);
+        verify(mBatteryStats).noteStopWakelock(uid, pid, "wakelockTag", /* historyTag= */ null,
+                BatteryStats.WAKE_TYPE_FULL);
+        verify(mAppOpsManager).finishOp(AppOpsManager.OP_WAKE_LOCK, uid,
+                "my.package.name", null);
+
+        clearInvocations(mWakeLockLog, mBatteryStats, mAppOpsManager);
+
+        // Acquire the wakelock
+        mNotifier.onWakeLockAcquired(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
+                "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
+                exceptingCallback);
+
+        mTestLooper.dispatchAll();
+        verify(mWakeLockLog).onWakeLockAcquired("wakelockTag", uid,
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK, -1);
+        verify(mBatteryStats).noteStartWakelock(uid, pid, "wakelockTag", /* historyTag= */ null,
+                BatteryStats.WAKE_TYPE_FULL, false);
+        verify(mAppOpsManager).startOpNoThrow(AppOpsManager.OP_WAKE_LOCK, uid,
+                "my.package.name", false, null, null);
+    }
+
     private final PowerManagerService.Injector mInjector = new PowerManagerService.Injector() {
         @Override
         Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
@@ -365,13 +479,17 @@
             public WakeLockLog getWakeLockLog(Context context) {
                 return mWakeLockLog;
             }
+
+            @Override
+            public AppOpsManager getAppOpsManager(Context context) {
+                return mAppOpsManager;
+            }
         };
 
         mNotifier = new Notifier(
                 mTestLooper.getLooper(),
                 mContextSpy,
-                IBatteryStats.Stub.asInterface(ServiceManager.getService(
-                        BatteryStats.SERVICE_NAME)),
+                mBatteryStats,
                 mInjector.createSuspendBlocker(mService, "testBlocker"),
                 null,
                 null,