Merge "Adding a logger to log long-press effect events" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 7a1add3..6b8baf8 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -99,6 +99,7 @@
         "framework_graphics_flags_java_lib",
         "hwui_flags_java_lib",
         "libcore_exported_aconfig_flags_lib",
+        "libgui_flags_java_lib",
         "power_flags_lib",
         "sdk_sandbox_flags_lib",
         "surfaceflinger_flags_java_lib",
@@ -1208,6 +1209,12 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "libgui_flags_java_lib",
+    aconfig_declarations: "libgui_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Content Capture
 aconfig_declarations {
     name: "android.view.contentcapture.flags-aconfig",
diff --git a/Android.bp b/Android.bp
index 6fa6650..7f4871f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -401,6 +401,7 @@
     ],
     sdk_version: "core_platform",
     static_libs: [
+        "aconfig_storage_reader_java",
         "android.hardware.common.fmq-V1-java",
         "bouncycastle-repackaged-unbundled",
         "com.android.sysprop.foldlockbehavior",
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index c3fe031..d92351d 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -1990,7 +1990,7 @@
             }
         }
         if (android.app.admin.flags.Flags.disallowUserControlBgUsageFix()) {
-            if (!Flags.avoidIdleCheck()) {
+            if (!Flags.avoidIdleCheck() || mInjector.getBootPhase() >= PHASE_BOOT_COMPLETED) {
                 postCheckIdleStates(userId);
             }
         }
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 531537c..c83dd65 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -304,6 +304,12 @@
     public boolean isTopActivityStyleFloating;
 
     /**
+     * The last non-fullscreen bounds the task was launched in or resized to.
+     * @hide
+     */
+    public Rect lastNonFullscreenBounds;
+
+    /**
      * The URI of the intent that generated the top-most activity opened using a URL.
      * @hide
      */
@@ -450,6 +456,7 @@
                 && Objects.equals(topActivity, that.topActivity)
                 && isTopActivityTransparent == that.isTopActivityTransparent
                 && isTopActivityStyleFloating == that.isTopActivityStyleFloating
+                && lastNonFullscreenBounds == this.lastNonFullscreenBounds
                 && Objects.equals(capturedLink, that.capturedLink)
                 && capturedLinkTimestamp == that.capturedLinkTimestamp
                 && appCompatTaskInfo.equalsForTaskOrganizer(that.appCompatTaskInfo);
@@ -522,6 +529,7 @@
         displayAreaFeatureId = source.readInt();
         isTopActivityTransparent = source.readBoolean();
         isTopActivityStyleFloating = source.readBoolean();
+        lastNonFullscreenBounds = source.readTypedObject(Rect.CREATOR);
         capturedLink = source.readTypedObject(Uri.CREATOR);
         capturedLinkTimestamp = source.readLong();
         appCompatTaskInfo = source.readTypedObject(AppCompatTaskInfo.CREATOR);
@@ -572,6 +580,7 @@
         dest.writeInt(displayAreaFeatureId);
         dest.writeBoolean(isTopActivityTransparent);
         dest.writeBoolean(isTopActivityStyleFloating);
+        dest.writeTypedObject(lastNonFullscreenBounds, flags);
         dest.writeTypedObject(capturedLink, flags);
         dest.writeLong(capturedLinkTimestamp);
         dest.writeTypedObject(appCompatTaskInfo, flags);
@@ -612,6 +621,7 @@
                 + " displayAreaFeatureId=" + displayAreaFeatureId
                 + " isTopActivityTransparent=" + isTopActivityTransparent
                 + " isTopActivityStyleFloating=" + isTopActivityStyleFloating
+                + " lastNonFullscreenBounds=" + lastNonFullscreenBounds
                 + " capturedLink=" + capturedLink
                 + " capturedLinkTimestamp=" + capturedLinkTimestamp
                 + " appCompatTaskInfo=" + appCompatTaskInfo
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index 55278f6..19de793 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -117,3 +117,11 @@
     description: "Enable high resolution scroll"
     bug: "335160780"
 }
+
+flag {
+  name: "camera_multiple_input_streams"
+  is_exported: true
+  namespace: "virtual_devices"
+  description: "Expose multiple surface for the virtual camera owner for different stream resolution"
+  bug: "341083465"
+}
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index f4e2a7e..62b3682 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -64,3 +64,13 @@
         purpose: PURPOSE_FEATURE
     }
 }
+
+flag {
+    namespace: "haptics"
+    name: "throttle_vibration_params_requests"
+    description: "Control the frequency of vibration params requests to prevent overloading the vendor service"
+    bug: "355320860"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 634469d..cf329d3 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -164,6 +164,9 @@
             float width, float height, float vecX, float vecY,
             float maxStretchAmountX, float maxStretchAmountY, float childRelativeLeft,
             float childRelativeTop, float childRelativeRight, float childRelativeBottom);
+    private static native void nativeSetEdgeExtensionEffect(long transactionObj, long nativeObj,
+                                                            boolean leftEdge, boolean rightEdge,
+                                                            boolean topEdge, boolean bottomEdge);
     private static native void nativeSetTrustedOverlay(long transactionObj, long nativeObject,
             int isTrustedOverlay);
     private static native void nativeSetDropInputMode(
@@ -3513,6 +3516,19 @@
         /**
          * @hide
          */
+        public Transaction setEdgeExtensionEffect(SurfaceControl sc, int edge) {
+            checkPreconditions(sc);
+
+            nativeSetEdgeExtensionEffect(
+                    mNativeObject, sc.mNativeObject,
+                    (edge & WindowInsets.Side.LEFT) != 0, (edge & WindowInsets.Side.RIGHT) != 0,
+                    (edge & WindowInsets.Side.TOP) != 0, (edge & WindowInsets.Side.BOTTOM) != 0);
+            return this;
+        }
+
+        /**
+         * @hide
+         */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
         public Transaction setLayerStack(SurfaceControl sc, int layerStack) {
             checkPreconditions(sc);
@@ -4882,4 +4898,5 @@
     public static void notifyShutdown() {
         nativeNotifyShutdown();
     }
+
 }
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
index f3cde43..376e66f 100644
--- a/core/java/android/view/accessibility/AccessibilityCache.java
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -19,6 +19,7 @@
 
 import static android.view.accessibility.AccessibilityNodeInfo.FOCUS_ACCESSIBILITY;
 
+import android.annotation.Nullable;
 import android.os.Build;
 import android.os.SystemClock;
 import android.util.ArraySet;
@@ -48,6 +49,8 @@
 
     private boolean mEnabled = true;
 
+    private final SparseArray<String> mWindowIdToEventSourceClassName = new SparseArray<>();
+
     /**
      * {@link AccessibilityEvent} types that are critical for the cache to stay up to date
      *
@@ -273,8 +276,11 @@
                     clearSubTreeLocked(event.getWindowId(), event.getSourceNodeId());
                 } break;
 
-                case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
+                case AccessibilityEvent.TYPE_WINDOWS_CHANGED: {
                     mValidWindowCacheTimeStamp = event.getEventTime();
+                    if (event.getWindowChanges() == AccessibilityEvent.WINDOWS_CHANGE_REMOVED) {
+                        mWindowIdToEventSourceClassName.remove(event.getWindowId());
+                    }
                     if (event.getWindowChanges()
                             == AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED) {
                         // Don't need to clear all cache. Unless the changes are related to
@@ -282,8 +288,15 @@
                         clearWindowCacheLocked();
                         break;
                     }
+                    clear();
+                }
+                break;
                 case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
                     mValidWindowCacheTimeStamp = event.getEventTime();
+                    if (event.getContentChangeTypes() == 0 && event.getClassName() != null) {
+                        mWindowIdToEventSourceClassName.put(event.getWindowId(),
+                                event.getClassName().toString());
+                    }
                     clear();
                 } break;
             }
@@ -907,6 +920,12 @@
         }
     }
 
+    /** Returns the source class associated with the window with the given id. */
+    @Nullable
+    public String getEventSourceClassName(int windowId) {
+        return mWindowIdToEventSourceClassName.get(windowId);
+    }
+
     // Layer of indirection included to break dependency chain for testing
     public static class AccessibilityNodeRefresher {
         /** Refresh the given AccessibilityNodeInfo object. */
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index 09306c7..2af935d 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -28,6 +28,7 @@
 import android.os.SystemProperties;
 import android.util.AttributeSet;
 import android.util.TypedValue;
+import android.view.WindowInsets;
 
 import dalvik.system.CloseGuard;
 
@@ -881,12 +882,13 @@
     }
 
     /**
-     * @return if a window animation has outsets applied to it.
+     * @return the edges to which outsets can be applied to
      *
      * @hide
      */
-    public boolean hasExtension() {
-        return false;
+    @WindowInsets.Side.InsetsSide
+    public int getExtensionEdges() {
+        return 0x0;
     }
 
     /**
diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java
index 5aaa994..bbdc9d0 100644
--- a/core/java/android/view/animation/AnimationSet.java
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -21,6 +21,7 @@
 import android.graphics.RectF;
 import android.os.Build;
 import android.util.AttributeSet;
+import android.view.WindowInsets;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -540,12 +541,12 @@
 
     /** @hide */
     @Override
-    public boolean hasExtension() {
+    @WindowInsets.Side.InsetsSide
+    public int getExtensionEdges() {
+        int edge = 0x0;
         for (Animation animation : mAnimations) {
-            if (animation.hasExtension()) {
-                return true;
-            }
+            edge |= animation.getExtensionEdges();
         }
-        return false;
+        return edge;
     }
 }
diff --git a/core/java/android/view/animation/ExtendAnimation.java b/core/java/android/view/animation/ExtendAnimation.java
index 210eb8a..ed047c7 100644
--- a/core/java/android/view/animation/ExtendAnimation.java
+++ b/core/java/android/view/animation/ExtendAnimation.java
@@ -20,6 +20,7 @@
 import android.content.res.TypedArray;
 import android.graphics.Insets;
 import android.util.AttributeSet;
+import android.view.WindowInsets;
 
 /**
  * An animation that controls the outset of an object.
@@ -50,6 +51,8 @@
     private float mToRightValue;
     private float mToBottomValue;
 
+    private int mExtensionEdges = 0x0;
+
     /**
      * Constructor used when an ExtendAnimation is loaded from a resource.
      *
@@ -151,9 +154,22 @@
 
     /** @hide */
     @Override
-    public boolean hasExtension() {
-        return mFromInsets.left < 0 || mFromInsets.top < 0 || mFromInsets.right < 0
-                || mFromInsets.bottom < 0;
+    @WindowInsets.Side.InsetsSide
+    public int getExtensionEdges() {
+        mExtensionEdges = 0x0;
+        if (mFromLeftValue > 0 || mToLeftValue > 0) {
+            mExtensionEdges |= WindowInsets.Side.LEFT;
+        }
+        if (mFromRightValue > 0 || mToRightValue > 0) {
+            mExtensionEdges |= WindowInsets.Side.RIGHT;
+        }
+        if (mFromTopValue > 0 || mToTopValue > 0) {
+            mExtensionEdges |= WindowInsets.Side.TOP;
+        }
+        if (mFromBottomValue > 0 || mToBottomValue > 0) {
+            mExtensionEdges |= WindowInsets.Side.BOTTOM;
+        }
+        return mExtensionEdges;
     }
 
     @Override
diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java
index ec4e3e9..3cfde87 100644
--- a/core/java/android/window/TransitionFilter.java
+++ b/core/java/android/window/TransitionFilter.java
@@ -30,6 +30,8 @@
 import android.os.Parcelable;
 import android.view.WindowManager;
 
+import com.android.window.flags.Flags;
+
 /**
  * A parcelable filter that can be used for rerouting transitions to a remote. This is a local
  * representation so that the transition system doesn't need to make blocking queries over
@@ -183,6 +185,9 @@
         public ComponentName mTopActivity;
         public IBinder mLaunchCookie;
 
+        /** If non-null, requires the change to specifically have or not-have a custom animation. */
+        public Boolean mCustomAnimation = null;
+
         public Requirement() {
         }
 
@@ -196,6 +201,9 @@
             mOrder = in.readInt();
             mTopActivity = in.readTypedObject(ComponentName.CREATOR);
             mLaunchCookie = in.readStrongBinder();
+            // 0: null, 1: false, 2: true
+            final int customAnimRaw = in.readInt();
+            mCustomAnimation = customAnimRaw == 0 ? null : Boolean.valueOf(customAnimRaw == 2);
         }
 
         /** Go through changes and find if at-least one change matches this filter */
@@ -237,6 +245,23 @@
                 if (!matchesCookie(change.getTaskInfo())) {
                     continue;
                 }
+                if (mCustomAnimation != null
+                        // only applies to activity/task
+                        && (change.getTaskInfo() != null
+                                || change.getActivityComponent() != null)) {
+                    final TransitionInfo.AnimationOptions opts =
+                            Flags.moveAnimationOptionsToChange() ? change.getAnimationOptions()
+                                    : info.getAnimationOptions();
+                    if (opts != null) {
+                        boolean canActuallyOverride = change.getTaskInfo() == null
+                                || opts.getOverrideTaskTransition();
+                        if (mCustomAnimation != canActuallyOverride) {
+                            continue;
+                        }
+                    } else if (mCustomAnimation) {
+                        continue;
+                    }
+                }
                 return true;
             }
             return false;
@@ -286,6 +311,8 @@
             dest.writeInt(mOrder);
             dest.writeTypedObject(mTopActivity, flags);
             dest.writeStrongBinder(mLaunchCookie);
+            int customAnimRaw = mCustomAnimation == null ? 0 : (mCustomAnimation ? 2 : 1);
+            dest.writeInt(customAnimRaw);
         }
 
         @NonNull
@@ -327,6 +354,9 @@
             out.append(" order=" + containerOrderToString(mOrder));
             out.append(" topActivity=").append(mTopActivity);
             out.append(" launchCookie=").append(mLaunchCookie);
+            if (mCustomAnimation != null) {
+                out.append(" customAnim=").append(mCustomAnimation.booleanValue());
+            }
             out.append("}");
             return out.toString();
         }
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 76989f9..725d496 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -158,17 +158,6 @@
 }
 
 flag {
-  name: "keyguard_appear_transition"
-  namespace: "windowing_frontend"
-  description: "Add transition when keyguard appears"
-  bug: "327970608"
-  is_fixed_read_only: true
-  metadata {
-      purpose: PURPOSE_BUGFIX
-  }
-}
-
-flag {
   name: "get_dimmer_on_closing"
   namespace: "windowing_frontend"
   description: "Change check for when to ignore a closing task's dim"
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 9ce7658..0f53164 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -22,6 +22,7 @@
 #include <android/graphics/properties.h>
 #include <android/graphics/region.h>
 #include <android/gui/BnWindowInfosReportedListener.h>
+#include <android/gui/EdgeExtensionParameters.h>
 #include <android/gui/JankData.h>
 #include <android/hardware/display/IDeviceProductInfoConstants.h>
 #include <android/os/IInputConstants.h>
@@ -799,6 +800,20 @@
     transaction->setStretchEffect(ctrl, stretch);
 }
 
+static void nativeSetEdgeExtensionEffect(JNIEnv* env, jclass clazz, jlong transactionObj,
+                                         jlong nativeObj, jboolean leftEdge, jboolean rightEdge,
+                                         jboolean topEdge, jboolean bottomEdge) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    auto* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObj);
+
+    auto effect = gui::EdgeExtensionParameters();
+    effect.extendLeft = leftEdge;
+    effect.extendRight = rightEdge;
+    effect.extendTop = topEdge;
+    effect.extendBottom = bottomEdge;
+    transaction->setEdgeExtensionEffect(ctrl, effect);
+}
+
 static void nativeSetFlags(JNIEnv* env, jclass clazz, jlong transactionObj,
         jlong nativeObject, jint flags, jint mask) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -2340,6 +2355,8 @@
             (void*)nativeSetBlurRegions },
     {"nativeSetStretchEffect", "(JJFFFFFFFFFF)V",
             (void*) nativeSetStretchEffect },
+    {"nativeSetEdgeExtensionEffect", "(JJZZZZ)V",
+            (void*) nativeSetEdgeExtensionEffect },
     {"nativeSetShadowRadius", "(JJF)V",
             (void*)nativeSetShadowRadius },
     {"nativeSetFrameRate", "(JJFII)V",
diff --git a/core/proto/android/app/appexitinfo.proto b/core/proto/android/app/appexitinfo.proto
index e560a94..8ee7962 100644
--- a/core/proto/android/app/appexitinfo.proto
+++ b/core/proto/android/app/appexitinfo.proto
@@ -20,7 +20,7 @@
 package android.app;
 
 import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/app_enums.proto";
+import "frameworks/proto_logging/stats/enums/app_shared/app_enums.proto";
 
 /**
  * An android.app.ApplicationExitInfo object.
diff --git a/core/proto/android/app/appstartinfo.proto b/core/proto/android/app/appstartinfo.proto
index c137533..8de5458 100644
--- a/core/proto/android/app/appstartinfo.proto
+++ b/core/proto/android/app/appstartinfo.proto
@@ -20,7 +20,7 @@
 package android.app;
 
 import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/app_enums.proto";
+import "frameworks/proto_logging/stats/enums/app_shared/app_enums.proto";
 
 /**
  * An android.app.ApplicationStartInfo object.
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 90069f1..58f39a9 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -35,7 +35,7 @@
 import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
 import "frameworks/base/core/proto/android/util/common.proto";
 import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/app_enums.proto";
+import "frameworks/proto_logging/stats/enums/app_shared/app_enums.proto";
 
 option java_multiple_files = true;
 
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index 593bbc6..8fd5d71 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -26,7 +26,7 @@
 import "frameworks/base/core/proto/android/providers/settings.proto";
 import "frameworks/base/core/proto/android/server/wirelesschargerdetector.proto";
 import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/app_enums.proto";
+import "frameworks/proto_logging/stats/enums/app_shared/app_enums.proto";
 import "frameworks/proto_logging/stats/enums/os/enums.proto";
 import "frameworks/proto_logging/stats/enums/view/enums.proto";
 
diff --git a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
index eebc578..ef7df59 100644
--- a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
+++ b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
@@ -29,9 +29,9 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityTestActivity;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.frameworks.coretests.R;
 
diff --git a/core/tests/coretests/src/android/text/BidiFormatterTest.java b/core/tests/coretests/src/android/text/BidiFormatterTest.java
index 312fb68..307c95b 100644
--- a/core/tests/coretests/src/android/text/BidiFormatterTest.java
+++ b/core/tests/coretests/src/android/text/BidiFormatterTest.java
@@ -20,8 +20,8 @@
 
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/DynamicLayoutBlocksTest.java b/core/tests/coretests/src/android/text/DynamicLayoutBlocksTest.java
index cca1ad3..81c4982 100644
--- a/core/tests/coretests/src/android/text/DynamicLayoutBlocksTest.java
+++ b/core/tests/coretests/src/android/text/DynamicLayoutBlocksTest.java
@@ -23,8 +23,8 @@
 
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java b/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
index 5939c06..5ff659b 100644
--- a/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
+++ b/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
@@ -23,8 +23,8 @@
 import android.platform.test.annotations.Presubmit;
 import android.text.method.OffsetMapping;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/DynamicLayoutTest.java b/core/tests/coretests/src/android/text/DynamicLayoutTest.java
index 699243b..1036928 100644
--- a/core/tests/coretests/src/android/text/DynamicLayoutTest.java
+++ b/core/tests/coretests/src/android/text/DynamicLayoutTest.java
@@ -30,8 +30,8 @@
 import android.text.style.ReplacementSpan;
 import android.util.ArraySet;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/EmojiConsistencyTest.java b/core/tests/coretests/src/android/text/EmojiConsistencyTest.java
index c6e9e9c..72d09c8 100644
--- a/core/tests/coretests/src/android/text/EmojiConsistencyTest.java
+++ b/core/tests/coretests/src/android/text/EmojiConsistencyTest.java
@@ -18,8 +18,8 @@
 
 import static junit.framework.Assert.assertEquals;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/EmojiTest.java b/core/tests/coretests/src/android/text/EmojiTest.java
index 0aeeb74..21f346e 100644
--- a/core/tests/coretests/src/android/text/EmojiTest.java
+++ b/core/tests/coretests/src/android/text/EmojiTest.java
@@ -22,8 +22,8 @@
 import android.icu.lang.UCharacterDirection;
 import android.icu.text.Bidi;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/LayoutBidiCursorPathTest.java b/core/tests/coretests/src/android/text/LayoutBidiCursorPathTest.java
index 96e7fb9..7728866 100644
--- a/core/tests/coretests/src/android/text/LayoutBidiCursorPathTest.java
+++ b/core/tests/coretests/src/android/text/LayoutBidiCursorPathTest.java
@@ -26,8 +26,8 @@
 import android.view.KeyEvent;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/LayoutTest.java b/core/tests/coretests/src/android/text/LayoutTest.java
index 98f8b7f..25f9cb7 100644
--- a/core/tests/coretests/src/android/text/LayoutTest.java
+++ b/core/tests/coretests/src/android/text/LayoutTest.java
@@ -42,8 +42,8 @@
 import android.text.style.ForegroundColorSpan;
 import android.text.style.StrikethroughSpan;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.google.common.truth.Expect;
 
diff --git a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
index 02b67e2..921a6bd 100644
--- a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
+++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
@@ -26,8 +26,8 @@
 import android.graphics.text.MeasuredText;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/PackedIntVectorTest.java b/core/tests/coretests/src/android/text/PackedIntVectorTest.java
index ba15b92..e8d706d 100644
--- a/core/tests/coretests/src/android/text/PackedIntVectorTest.java
+++ b/core/tests/coretests/src/android/text/PackedIntVectorTest.java
@@ -20,8 +20,8 @@
 
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/SpanColorsTest.java b/core/tests/coretests/src/android/text/SpanColorsTest.java
index 3d8d8f9..d2cb8c1 100644
--- a/core/tests/coretests/src/android/text/SpanColorsTest.java
+++ b/core/tests/coretests/src/android/text/SpanColorsTest.java
@@ -25,8 +25,8 @@
 import android.text.style.ImageSpan;
 import android.text.style.UnderlineSpan;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java b/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java
index 91b8c6a..b725133 100644
--- a/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java
+++ b/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java
@@ -25,8 +25,8 @@
 import android.text.style.SubscriptSpan;
 import android.text.style.UnderlineSpan;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java b/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java
index 9149f7b..a2952f6 100644
--- a/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java
+++ b/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java
@@ -24,8 +24,8 @@
 import android.text.style.QuoteSpan;
 import android.text.style.UnderlineSpan;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/SpannableTest.java b/core/tests/coretests/src/android/text/SpannableTest.java
index d248a1f..a3e6a78 100644
--- a/core/tests/coretests/src/android/text/SpannableTest.java
+++ b/core/tests/coretests/src/android/text/SpannableTest.java
@@ -21,8 +21,8 @@
 import android.platform.test.annotations.Presubmit;
 import android.test.MoreAsserts;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java b/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java
index ca43733..3e2516f 100644
--- a/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java
+++ b/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java
@@ -24,8 +24,8 @@
 import android.text.style.QuoteSpan;
 import android.text.style.UnderlineSpan;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/SpannedTest.java b/core/tests/coretests/src/android/text/SpannedTest.java
index 3ab0755..e9a357c 100644
--- a/core/tests/coretests/src/android/text/SpannedTest.java
+++ b/core/tests/coretests/src/android/text/SpannedTest.java
@@ -26,8 +26,8 @@
 import android.text.style.TextAppearanceSpan;
 import android.text.style.TypefaceSpan;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
index 32370b3e..3deda8c 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
@@ -21,8 +21,8 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.Log;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java b/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java
index 4221ac2..bc7efe4 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java
@@ -22,8 +22,8 @@
 import android.text.Layout.Directions;
 import android.text.StaticLayoutTest.LayoutBuilder;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index 0ebf03f..3541900 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -31,8 +31,8 @@
 import android.text.style.LocaleSpan;
 import android.util.Log;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTextMeasuringTest.java b/core/tests/coretests/src/android/text/StaticLayoutTextMeasuringTest.java
index 0d42326..b32e94a 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTextMeasuringTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTextMeasuringTest.java
@@ -21,8 +21,8 @@
 
 import android.text.Layout.Alignment;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/TextLayoutTest.java b/core/tests/coretests/src/android/text/TextLayoutTest.java
index 15fbc9e..1584bc3 100644
--- a/core/tests/coretests/src/android/text/TextLayoutTest.java
+++ b/core/tests/coretests/src/android/text/TextLayoutTest.java
@@ -18,8 +18,8 @@
 
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/TextLineTest.java b/core/tests/coretests/src/android/text/TextLineTest.java
index 8ae5669..2997853 100644
--- a/core/tests/coretests/src/android/text/TextLineTest.java
+++ b/core/tests/coretests/src/android/text/TextLineTest.java
@@ -30,10 +30,10 @@
 import android.text.style.ReplacementSpan;
 import android.text.style.TabStopSpan;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.test.filters.Suppress;
 import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/TextShaperTest.java b/core/tests/coretests/src/android/text/TextShaperTest.java
index 77b14e6..84112ae 100644
--- a/core/tests/coretests/src/android/text/TextShaperTest.java
+++ b/core/tests/coretests/src/android/text/TextShaperTest.java
@@ -21,8 +21,8 @@
 import android.graphics.fonts.Font;
 import android.graphics.fonts.FontFileUtil;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/TextUtilsTest.java b/core/tests/coretests/src/android/text/TextUtilsTest.java
index c4bcfd4..f552265 100644
--- a/core/tests/coretests/src/android/text/TextUtilsTest.java
+++ b/core/tests/coretests/src/android/text/TextUtilsTest.java
@@ -34,9 +34,9 @@
 import android.text.util.Rfc822Tokenizer;
 import android.view.View;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.google.android.collect.Lists;
 
diff --git a/core/tests/coretests/src/android/text/VariationParserTest.java b/core/tests/coretests/src/android/text/VariationParserTest.java
index 0afe811..8e93dd4 100644
--- a/core/tests/coretests/src/android/text/VariationParserTest.java
+++ b/core/tests/coretests/src/android/text/VariationParserTest.java
@@ -22,8 +22,8 @@
 import android.graphics.fonts.FontVariationAxis;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/format/DateFormatTest.java b/core/tests/coretests/src/android/text/format/DateFormatTest.java
index 212cc44..59af6dd 100644
--- a/core/tests/coretests/src/android/text/format/DateFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateFormatTest.java
@@ -25,8 +25,8 @@
 import android.icu.text.DateFormatSymbols;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
diff --git a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
index 9750de3..a07d399 100644
--- a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
@@ -42,8 +42,8 @@
 import android.icu.util.ULocale;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/format/DateUtilsTest.java b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
index 381c051..47be893 100644
--- a/core/tests/coretests/src/android/text/format/DateUtilsTest.java
+++ b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
@@ -23,8 +23,8 @@
 import android.os.LocaleList;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/core/tests/coretests/src/android/text/format/FormatterTest.java b/core/tests/coretests/src/android/text/format/FormatterTest.java
index 986cee5..555292e 100644
--- a/core/tests/coretests/src/android/text/format/FormatterTest.java
+++ b/core/tests/coretests/src/android/text/format/FormatterTest.java
@@ -28,8 +28,8 @@
 import android.text.format.Formatter.BytesResult;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
index 2337802..cd31950 100644
--- a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
+++ b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
@@ -36,8 +36,8 @@
 
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/format/TimeMigrationUtilsTest.java b/core/tests/coretests/src/android/text/format/TimeMigrationUtilsTest.java
index b605520..c8cb5f3 100644
--- a/core/tests/coretests/src/android/text/format/TimeMigrationUtilsTest.java
+++ b/core/tests/coretests/src/android/text/format/TimeMigrationUtilsTest.java
@@ -20,8 +20,8 @@
 
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/core/tests/coretests/src/android/text/format/TimeTest.java b/core/tests/coretests/src/android/text/format/TimeTest.java
index ac00411..6138ea1 100644
--- a/core/tests/coretests/src/android/text/format/TimeTest.java
+++ b/core/tests/coretests/src/android/text/format/TimeTest.java
@@ -24,9 +24,9 @@
 import android.util.Log;
 import android.util.TimeFormatException;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.test.filters.Suppress;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/method/BackspaceTest.java b/core/tests/coretests/src/android/text/method/BackspaceTest.java
index 19c2c61..a7ff244 100644
--- a/core/tests/coretests/src/android/text/method/BackspaceTest.java
+++ b/core/tests/coretests/src/android/text/method/BackspaceTest.java
@@ -24,8 +24,8 @@
 import android.widget.TextView.BufferType;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java b/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java
index 652622d..1e4024d 100644
--- a/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java
+++ b/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java
@@ -24,8 +24,8 @@
 import android.widget.TextView.BufferType;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
index 9ef137b..2f336ab 100644
--- a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
+++ b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
@@ -27,9 +27,8 @@
 import android.view.View;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
 
 import org.junit.BeforeClass;
 import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/method/WordIteratorTest.java b/core/tests/coretests/src/android/text/method/WordIteratorTest.java
index cc345f5..046496a 100644
--- a/core/tests/coretests/src/android/text/method/WordIteratorTest.java
+++ b/core/tests/coretests/src/android/text/method/WordIteratorTest.java
@@ -23,8 +23,8 @@
 
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/style/UnderlineSpanTest.java b/core/tests/coretests/src/android/text/style/UnderlineSpanTest.java
index a0d2f85..043960d0 100644
--- a/core/tests/coretests/src/android/text/style/UnderlineSpanTest.java
+++ b/core/tests/coretests/src/android/text/style/UnderlineSpanTest.java
@@ -24,8 +24,8 @@
 import android.text.StaticLayout;
 import android.text.TextPaint;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/util/LinkifyTest.java b/core/tests/coretests/src/android/text/util/LinkifyTest.java
index 107ecd7..52f3b2e 100644
--- a/core/tests/coretests/src/android/text/util/LinkifyTest.java
+++ b/core/tests/coretests/src/android/text/util/LinkifyTest.java
@@ -31,8 +31,8 @@
 import android.widget.TextView;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
index dd8cc6e..e5ad561 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
@@ -42,8 +42,8 @@
 import android.view.Display;
 import android.view.View;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.google.common.base.Throwables;
 
@@ -1053,6 +1053,28 @@
         assertFalse(mAccessibilityCache.isNodeInCache(childInfo));
     }
 
+    @Test
+    public void getEventSourceClassName_windowStateChangedThenRemoved() {
+        final String sourceActivityClassName = "com.example.SomeActivity";
+        final AccessibilityEvent windowStateChangedEvent = new AccessibilityEvent(
+                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+        final View mockView = getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1);
+        windowStateChangedEvent.setSource(mockView);
+        windowStateChangedEvent.setClassName(sourceActivityClassName);
+
+        mAccessibilityCache.onAccessibilityEvent(windowStateChangedEvent);
+        assertEquals(mAccessibilityCache.getEventSourceClassName(WINDOW_ID_1),
+                sourceActivityClassName);
+
+        final AccessibilityEvent windowRemovedEvent = new AccessibilityEvent(
+                AccessibilityEvent.TYPE_WINDOWS_CHANGED);
+        windowRemovedEvent.setWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_REMOVED);
+        windowRemovedEvent.setSource(mockView);
+
+        mAccessibilityCache.onAccessibilityEvent(windowRemovedEvent);
+        assertNull(mAccessibilityCache.getEventSourceClassName(WINDOW_ID_1));
+    }
+
     private AccessibilityWindowInfo obtainAccessibilityWindowInfo(int windowId, int layer) {
         AccessibilityWindowInfo windowInfo = AccessibilityWindowInfo.obtain();
         windowInfo.setId(windowId);
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java
index ddc27aa..3b8f66a 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java
@@ -23,7 +23,7 @@
 import android.os.Parcel;
 import android.view.Display;
 
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
index 3e061d2..eb482f2e 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
@@ -26,8 +26,8 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import libcore.util.EmptyArray;
 
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
index ce36ee0..82e3427 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
@@ -55,7 +55,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.internal.R;
 import com.android.internal.accessibility.common.ShortcutConstants;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 3d4918b..a5137bdf 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -28,8 +28,8 @@
 import android.util.ArraySet;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.CollectionUtils;
 
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
index 9f5ed29..37625e2 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
@@ -67,8 +67,8 @@
 
 import androidx.lifecycle.Lifecycle;
 import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.R;
 import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity;
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityTargetTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityTargetTest.java
index f01ac6f..8608f6c 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityTargetTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityTargetTest.java
@@ -27,7 +27,7 @@
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Flags;
 
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.internal.accessibility.common.ShortcutConstants;
 
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
index 9cac312..5339d91 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
@@ -41,8 +41,8 @@
 import android.view.accessibility.Flags;
 import android.view.accessibility.IAccessibilityManager;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.accessibility.TestUtils;
 import com.android.internal.accessibility.common.ShortcutConstants;
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/util/AccessibilityUtilsTest.java b/core/tests/coretests/src/com/android/internal/accessibility/util/AccessibilityUtilsTest.java
index 58ab92a..f37ec9b 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/util/AccessibilityUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/util/AccessibilityUtilsTest.java
@@ -33,7 +33,7 @@
 import android.text.SpannableString;
 import android.text.style.LocaleSpan;
 
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 8d30db6..86e0f14 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -146,6 +146,11 @@
     /** To be overridden by subclasses to adjust the animation surface change. */
     void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
         // Update the surface position and alpha.
+        if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()
+                && mAnimation.getExtensionEdges() != 0) {
+            t.setEdgeExtensionEffect(mLeash, mAnimation.getExtensionEdges());
+        }
+
         mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
         t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
         t.setAlpha(mLeash, mTransformation.getAlpha());
@@ -165,7 +170,7 @@
         if (!cropRect.intersect(mWholeAnimationBounds)) {
             // Hide the surface when it is outside of the animation area.
             t.setAlpha(mLeash, 0);
-        } else if (mAnimation.hasExtension()) {
+        } else if (mAnimation.getExtensionEdges() != 0) {
             // Allow the surface to be shown in its original bounds in case we want to use edge
             // extensions.
             cropRect.union(mContentBounds);
@@ -180,6 +185,7 @@
     @CallSuper
     void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
         onAnimationUpdate(t, mAnimation.getDuration());
+        t.setEdgeExtensionEffect(mLeash, /* edge */ 0);
     }
 
     final long getDurationHint() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 5696a54..d2cef4b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -144,8 +144,10 @@
             // ending states.
             prepareForJumpCut(info, startTransaction);
         } else {
-            addEdgeExtensionIfNeeded(startTransaction, finishTransaction,
-                    postStartTransactionCallbacks, adapters);
+            if (!com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) {
+                addEdgeExtensionIfNeeded(startTransaction, finishTransaction,
+                        postStartTransactionCallbacks, adapters);
+            }
             addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters);
             for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
                 duration = Math.max(duration, adapter.getDurationHint());
@@ -341,7 +343,7 @@
             @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
         for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
             final Animation animation = adapter.mAnimation;
-            if (!animation.hasExtension()) {
+            if (animation.getExtensionEdges() == 0) {
                 continue;
             }
             if (adapter.mChange.hasFlags(FLAG_TRANSLUCENT)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index df3803d..999ab95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -416,6 +416,17 @@
         // location now.
         mSpringingToTouch = false;
 
+        // Boost the velocityX if it's zero to forcefully push it towards the nearest edge.
+        // We don't simply change the xEndValue below since the PhysicsAnimator would rely on the
+        // same velocityX to find out which edge to snap to.
+        if (velocityX == 0) {
+            final int motionCenterX = mPipBoundsState
+                    .getMotionBoundsState().getBoundsInMotion().centerX();
+            final int displayCenterX = mPipBoundsState
+                    .getDisplayBounds().centerX();
+            velocityX = (motionCenterX < displayCenterX) ? -0.001f : 0.001f;
+        }
+
         mTemporaryBoundsPhysicsAnimator
                 .spring(FloatProperties.RECT_WIDTH, getBounds().width(), mSpringConfig)
                 .spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mSpringConfig)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index ea02de9d..e1e072a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -416,6 +416,17 @@
         // location now.
         mSpringingToTouch = false;
 
+        // Boost the velocityX if it's zero to forcefully push it towards the nearest edge.
+        // We don't simply change the xEndValue below since the PhysicsAnimator would rely on the
+        // same velocityX to find out which edge to snap to.
+        if (velocityX == 0) {
+            final int motionCenterX = mPipBoundsState
+                    .getMotionBoundsState().getBoundsInMotion().centerX();
+            final int displayCenterX = mPipBoundsState
+                    .getDisplayBounds().centerX();
+            velocityX = (motionCenterX < displayCenterX) ? -0.001f : 0.001f;
+        }
+
         mTemporaryBoundsPhysicsAnimator
                 .spring(FloatProperties.RECT_WIDTH, getBounds().width(), mSpringConfig)
                 .spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mSpringConfig)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 7784784..d8c8c60 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -502,7 +502,8 @@
                 backgroundColorForTransition = getTransitionBackgroundColorIfSet(info, change, a,
                         backgroundColorForTransition);
 
-                if (!isTask && a.hasExtension()) {
+                if (!com.android.graphics.libgui.flags.Flags.edgeExtensionShader() && !isTask
+                        && a.getExtensionEdges() != 0) {
                     if (!TransitionUtil.isOpeningType(mode)) {
                         // Can screenshot now (before startTransaction is applied)
                         edgeExtendWindow(change, a, startTransaction, finishTransaction);
@@ -512,6 +513,8 @@
                         postStartTransactionCallbacks
                                 .add(t -> edgeExtendWindow(change, a, t, finishTransaction));
                     }
+                } else if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) {
+                    finishTransaction.setEdgeExtensionEffect(change.getLeash(), /* edge */ 0);
                 }
 
                 final Rect clipRect = TransitionUtil.isClosingType(mode)
@@ -1008,6 +1011,10 @@
             Point position, float cornerRadius, @Nullable Rect immutableClipRect) {
         tmpTransformation.clear();
         anim.getTransformation(time, tmpTransformation);
+        if (anim.getExtensionEdges() != 0
+                && com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) {
+            t.setEdgeExtensionEffect(leash, anim.getExtensionEdges());
+        }
         if (position != null) {
             tmpTransformation.getMatrix().postTranslate(position.x, position.y);
         }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 409b877..81e6d07 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -442,6 +442,27 @@
     }
 
     @Test
+    public void testTransitionFilterAnimOverride() {
+        TransitionFilter filter = new TransitionFilter();
+        filter.mRequirements =
+                new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()};
+        filter.mRequirements[0].mCustomAnimation = true;
+        filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
+
+        final RunningTaskInfo taskInf = createTaskInfo(1);
+        final TransitionInfo openTask = new TransitionInfoBuilder(TRANSIT_OPEN)
+                .addChange(TRANSIT_OPEN, taskInf).build();
+        assertFalse(filter.matches(openTask));
+
+        final TransitionInfo.AnimationOptions overOpts =
+                TransitionInfo.AnimationOptions.makeCustomAnimOptions("pakname", 0, 0, 0, true);
+        final TransitionInfo openTaskOpts = new TransitionInfoBuilder(TRANSIT_OPEN)
+                .addChange(TRANSIT_OPEN, taskInf).build();
+        openTaskOpts.getChanges().get(0).setAnimationOptions(overOpts);
+        assertTrue(filter.matches(openTaskOpts));
+    }
+
+    @Test
     public void testRegisteredRemoteTransition() {
         Transitions transitions = createTestTransitions();
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
index e78b8a7..99d5891 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
@@ -62,6 +62,9 @@
     /** Whether the device is in audio sharing. */
     val inAudioSharing: Flow<Boolean>
 
+    /** The primary headset groupId in audio sharing. */
+    val primaryGroupId: StateFlow<Int>
+
     /** The secondary headset groupId in audio sharing. */
     val secondaryGroupId: StateFlow<Int>
 
@@ -109,6 +112,16 @@
         awaitClose { contentResolver.unregisterContentObserver(callback) }
     }
 
+    override val primaryGroupId: StateFlow<Int> =
+        primaryChange
+            .map { BluetoothUtils.getPrimaryGroupIdForBroadcast(contentResolver) }
+            .onStart { emit(BluetoothUtils.getPrimaryGroupIdForBroadcast(contentResolver)) }
+            .flowOn(backgroundCoroutineContext)
+            .stateIn(
+                coroutineScope,
+                SharingStarted.WhileSubscribed(),
+                BluetoothUtils.getPrimaryGroupIdForBroadcast(contentResolver))
+
     override val secondaryGroupId: StateFlow<Int> =
         merge(
                 btManager.profileManager.leAudioBroadcastAssistantProfile
@@ -121,7 +134,7 @@
                                 BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
                     }
                     .map { getSecondaryGroupId() },
-                primaryChange.map { getSecondaryGroupId() })
+                primaryGroupId.map { getSecondaryGroupId() })
             .onStart { emit(getSecondaryGroupId()) }
             .flowOn(backgroundCoroutineContext)
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), getSecondaryGroupId())
@@ -193,6 +206,8 @@
 
 class AudioSharingRepositoryEmptyImpl : AudioSharingRepository {
     override val inAudioSharing: Flow<Boolean> = flowOf(false)
+    override val primaryGroupId: StateFlow<Int> =
+        MutableStateFlow(BluetoothCsipSetCoordinator.GROUP_ID_INVALID)
     override val secondaryGroupId: StateFlow<Int> =
         MutableStateFlow(BluetoothCsipSetCoordinator.GROUP_ID_INVALID)
     override val volumeMap: StateFlow<GroupIdToVolumes> = MutableStateFlow(emptyMap())
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
index 94595d3..c54a2e4 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
@@ -131,6 +131,10 @@
         `when`(deviceManager.findDevice(device2)).thenReturn(cachedDevice2)
         `when`(receiveState.bisSyncState).thenReturn(arrayListOf(TEST_RECEIVE_STATE_CONTENT))
         `when`(assistant.getAllSources(any())).thenReturn(listOf(receiveState))
+        Settings.Secure.putInt(
+            contentResolver,
+            BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+            TEST_GROUP_ID_INVALID)
         underTest =
             AudioSharingRepositoryImpl(
                 contentResolver,
@@ -156,6 +160,22 @@
     }
 
     @Test
+    fun primaryGroupIdChange_emitValues() {
+        testScope.runTest {
+            val groupIds = mutableListOf<Int?>()
+            underTest.primaryGroupId.onEach { groupIds.add(it) }.launchIn(backgroundScope)
+            runCurrent()
+            triggerContentObserverChange()
+            runCurrent()
+
+            Truth.assertThat(groupIds)
+                .containsExactly(
+                    TEST_GROUP_ID_INVALID,
+                    TEST_GROUP_ID2)
+        }
+    }
+
+    @Test
     fun secondaryGroupIdChange_emitValues() {
         testScope.runTest {
             val groupIds = mutableListOf<Int?>()
@@ -217,7 +237,7 @@
     fun setSecondaryVolume_setValue() {
         testScope.runTest {
             Settings.Secure.putInt(
-                context.contentResolver,
+                contentResolver,
                 BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
                 TEST_GROUP_ID2)
             `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
@@ -248,7 +268,7 @@
     private fun triggerSourceAdded() {
         verify(assistant).registerServiceCallBack(any(), assistantCallbackCaptor.capture())
         Settings.Secure.putInt(
-            context.contentResolver,
+            contentResolver,
             BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
             TEST_GROUP_ID1)
         `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
@@ -259,7 +279,7 @@
         verify(assistant).registerServiceCallBack(any(), assistantCallbackCaptor.capture())
         `when`(assistant.allConnectedDevices).thenReturn(listOf(device1))
         Settings.Secure.putInt(
-            context.contentResolver,
+            contentResolver,
             BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
             TEST_GROUP_ID1)
         assistantCallbackCaptor.value.sourceRemoved(device2)
@@ -269,7 +289,7 @@
         verify(eventManager).registerCallback(btCallbackCaptor.capture())
         `when`(assistant.allConnectedDevices).thenReturn(listOf(device1))
         Settings.Secure.putInt(
-            context.contentResolver,
+            contentResolver,
             BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
             TEST_GROUP_ID1)
         btCallbackCaptor.value.onProfileConnectionStateChanged(cachedDevice2, state, profile)
@@ -283,7 +303,7 @@
                 contentObserverCaptor.capture())
         `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
         Settings.Secure.putInt(
-            context.contentResolver,
+            contentResolver,
             BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
             TEST_GROUP_ID2)
         contentObserverCaptor.value.primaryChanged()
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index e4f27aa..27f4305 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1217,6 +1217,9 @@
   namespace: "systemui"
   description: "Enables fullscreen vertical swiping in hub mode to bring up and down the bouncer and shade"
   bug: "340177049"
+  metadata {
+      purpose: PURPOSE_BUGFIX
+  }
 }
 
 flag {
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 b4e513c..e29e0fd 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
@@ -69,8 +69,10 @@
 import androidx.compose.foundation.lazy.grid.LazyGridState
 import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
 import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.selection.selectable
 import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Add
 import androidx.compose.material.icons.filled.Check
@@ -92,6 +94,7 @@
 import androidx.compose.material3.Text
 import androidx.compose.material3.rememberModalBottomSheetState
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.State
 import androidx.compose.runtime.derivedStateOf
@@ -145,6 +148,7 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
 import androidx.compose.ui.unit.times
 import androidx.compose.ui.util.fastAll
 import androidx.compose.ui.viewinterop.AndroidView
@@ -1000,7 +1004,9 @@
         shape = RoundedCornerShape(68.adjustedDp, 34.adjustedDp, 68.adjustedDp, 34.adjustedDp)
     ) {
         Column(
-            modifier = Modifier.fillMaxSize().padding(vertical = 32.dp, horizontal = 50.dp),
+            modifier =
+                Modifier.fillMaxSize()
+                    .padding(vertical = 32.adjustedDp, horizontal = 50.adjustedDp),
             verticalArrangement = Arrangement.Center,
             horizontalAlignment = Alignment.CenterHorizontally,
         ) {
@@ -1009,47 +1015,57 @@
                 contentDescription = stringResource(R.string.cta_label_to_open_widget_picker),
                 modifier = Modifier.size(Dimensions.IconSize).clearAndSetSemantics {},
             )
-            Spacer(modifier = Modifier.size(6.dp))
+            Spacer(modifier = Modifier.size(6.adjustedDp))
             Text(
                 text = stringResource(R.string.cta_label_to_edit_widget),
                 style = MaterialTheme.typography.titleLarge,
                 fontSize = nonScalableTextSize(22.dp),
                 lineHeight = nonScalableTextSize(28.dp),
+                modifier = Modifier.verticalScroll(rememberScrollState()).weight(1F)
             )
-            Spacer(modifier = Modifier.size(16.dp))
+            Spacer(modifier = Modifier.size(16.adjustedDp))
             Row(
-                modifier = Modifier.fillMaxWidth().height(56.dp),
-                horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterHorizontally),
+                modifier = Modifier.fillMaxWidth().height(56.adjustedDp),
+                horizontalArrangement =
+                    Arrangement.spacedBy(16.adjustedDp, Alignment.CenterHorizontally),
             ) {
-                OutlinedButton(
-                    modifier = Modifier.fillMaxHeight(),
-                    colors =
-                        ButtonDefaults.buttonColors(
-                            contentColor = colors.onPrimary,
-                        ),
-                    border = BorderStroke(width = 1.0.dp, color = colors.primaryContainer),
-                    contentPadding = PaddingValues(26.dp, 8.dp),
-                    onClick = viewModel::onDismissCtaTile,
+                CompositionLocalProvider(
+                    LocalDensity provides
+                        Density(
+                            LocalDensity.current.density,
+                            LocalDensity.current.fontScale.coerceIn(0f, 1.25f)
+                        )
                 ) {
-                    Text(
-                        text = stringResource(R.string.cta_tile_button_to_dismiss),
-                        fontSize = nonScalableTextSize(14.dp),
-                    )
-                }
-                Button(
-                    modifier = Modifier.fillMaxHeight(),
-                    colors =
-                        ButtonDefaults.buttonColors(
-                            containerColor = colors.primaryContainer,
-                            contentColor = colors.onPrimaryContainer,
-                        ),
-                    contentPadding = PaddingValues(26.dp, 8.dp),
-                    onClick = viewModel::onOpenWidgetEditor
-                ) {
-                    Text(
-                        text = stringResource(R.string.cta_tile_button_to_open_widget_editor),
-                        fontSize = nonScalableTextSize(14.dp),
-                    )
+                    OutlinedButton(
+                        modifier = Modifier.fillMaxHeight().weight(1F),
+                        colors =
+                            ButtonDefaults.buttonColors(
+                                contentColor = colors.onPrimary,
+                            ),
+                        border = BorderStroke(width = 1.0.dp, color = colors.primaryContainer),
+                        onClick = viewModel::onDismissCtaTile,
+                        contentPadding = PaddingValues(0.dp, 0.dp, 0.dp, 0.dp),
+                    ) {
+                        Text(
+                            text = stringResource(R.string.cta_tile_button_to_dismiss),
+                            fontSize = 14.sp,
+                        )
+                    }
+                    Button(
+                        modifier = Modifier.fillMaxHeight().weight(1F),
+                        colors =
+                            ButtonDefaults.buttonColors(
+                                containerColor = colors.primaryContainer,
+                                contentColor = colors.onPrimaryContainer,
+                            ),
+                        onClick = viewModel::onOpenWidgetEditor,
+                        contentPadding = PaddingValues(0.dp, 0.dp, 0.dp, 0.dp),
+                    ) {
+                        Text(
+                            text = stringResource(R.string.cta_tile_button_to_open_widget_editor),
+                            fontSize = 14.sp,
+                        )
+                    }
                 }
             }
         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index c4970c5..76a7a10 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -290,6 +290,7 @@
     val isCurrentGestureOverscroll =
         viewModel.isCurrentGestureOverscroll.collectAsStateWithLifecycle(false)
     val expansionFraction by viewModel.expandFraction.collectAsStateWithLifecycle(0f)
+    val shadeToQsFraction by viewModel.shadeToQsFraction.collectAsStateWithLifecycle(0f)
 
     val topPadding = dimensionResource(id = R.dimen.notification_side_paddings)
     val navBarHeight = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding()
@@ -385,14 +386,26 @@
             modifier
                 .element(Notifications.Elements.NotificationScrim)
                 .offset {
-                    // if scrim is expanded while transitioning to Gone scene, increase the offset
-                    // in step with the transition so that it is 0 when it completes.
+                    // if scrim is expanded while transitioning to Gone or QS scene, increase the
+                    // offset in step with the corresponding transition so that it is 0 when it
+                    // completes.
                     if (
                         scrimOffset.value < 0 &&
                             layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Gone) ||
                             layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen)
                     ) {
                         IntOffset(x = 0, y = (scrimOffset.value * expansionFraction).roundToInt())
+                    } else if (
+                        scrimOffset.value < 0 &&
+                            layoutState.isTransitioning(
+                                from = Scenes.Shade,
+                                to = Scenes.QuickSettings
+                            )
+                    ) {
+                        IntOffset(
+                            x = 0,
+                            y = (scrimOffset.value * (1 - shadeToQsFraction)).roundToInt()
+                        )
                     } else {
                         IntOffset(x = 0, y = scrimOffset.value.roundToInt())
                     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 242e822..e2a6a55 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -35,6 +35,8 @@
 import android.view.accessibility.AccessibilityManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.app.viewcapture.ViewCapture
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
@@ -70,6 +72,7 @@
 import com.android.systemui.testKosmos
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
@@ -108,6 +111,7 @@
 
     @Mock private lateinit var inflater: LayoutInflater
     @Mock private lateinit var windowManager: WindowManager
+    @Mock private lateinit var lazyViewCapture: kotlin.Lazy<ViewCapture>
     @Mock private lateinit var accessibilityManager: AccessibilityManager
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@@ -192,7 +196,8 @@
             UdfpsControllerOverlay(
                 context,
                 inflater,
-                windowManager,
+                ViewCaptureAwareWindowManager(windowManager, lazyViewCapture,
+                        isViewCaptureEnabled = false),
                 accessibilityManager,
                 statusBarStateController,
                 statusBarKeyguardViewManager,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 54e0725..d86890b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -65,12 +65,12 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
-import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.internal.util.LatencyTracker;
 import com.android.keyguard.KeyguardUpdateMonitor;
@@ -87,6 +87,7 @@
 import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel;
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.camera.CameraGestureHelper;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
 import com.android.systemui.dump.DumpManager;
@@ -118,6 +119,8 @@
 
 import dagger.Lazy;
 
+import javax.inject.Provider;
+
 import kotlinx.coroutines.CoroutineScope;
 
 import org.junit.Before;
@@ -152,7 +155,7 @@
     @Mock
     private FingerprintManager mFingerprintManager;
     @Mock
-    private WindowManager mWindowManager;
+    private ViewCaptureAwareWindowManager mWindowManager;
     @Mock
     private StatusBarStateController mStatusBarStateController;
     @Mock
@@ -261,6 +264,8 @@
     private Lazy<DeviceEntryUdfpsTouchOverlayViewModel> mDeviceEntryUdfpsTouchOverlayViewModel;
     @Mock
     private Lazy<DefaultUdfpsTouchOverlayViewModel> mDefaultUdfpsTouchOverlayViewModel;
+    @Mock
+    private Provider<CameraGestureHelper> mCameraGestureHelper;
 
     @Before
     public void setUp() {
@@ -269,7 +274,8 @@
                 mPowerRepository,
                 mock(FalsingCollector.class),
                 mock(ScreenOffAnimationController.class),
-                mStatusBarStateController
+                mStatusBarStateController,
+                mCameraGestureHelper
         );
         mPowerRepository.updateWakefulness(
                 WakefulnessState.AWAKE,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
similarity index 67%
rename from packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
index bea0db6..a0928ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
@@ -18,6 +18,7 @@
 
 import android.app.ActivityManager
 import android.app.IActivityTaskManager
+import android.app.admin.DevicePolicyManager
 import android.content.ComponentName
 import android.content.ContentResolver
 import android.content.Intent
@@ -29,82 +30,73 @@
 import com.android.systemui.ActivityIntentHelper
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.KotlinArgumentCaptor
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import com.google.common.util.concurrent.MoreExecutors
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.isNull
 import org.mockito.Mock
-import org.mockito.Mockito.any
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CameraGestureHelperTest : SysuiTestCase() {
 
-    @Mock
-    lateinit var centralSurfaces: CentralSurfaces
-    @Mock
-    lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
-    @Mock
-    lateinit var keyguardStateController: KeyguardStateController
-    @Mock
-    lateinit var packageManager: PackageManager
-    @Mock
-    lateinit var activityManager: ActivityManager
-    @Mock
-    lateinit var activityStarter: ActivityStarter
-    @Mock
-    lateinit var activityIntentHelper: ActivityIntentHelper
-    @Mock
-    lateinit var activityTaskManager: IActivityTaskManager
-    @Mock
-    lateinit var cameraIntents: CameraIntentsWrapper
-    @Mock
-    lateinit var contentResolver: ContentResolver
-    @Mock
-    lateinit var mSelectedUserInteractor: SelectedUserInteractor
+    @Mock lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+    @Mock lateinit var keyguardStateController: KeyguardStateController
+    @Mock lateinit var packageManager: PackageManager
+    @Mock lateinit var activityManager: ActivityManager
+    @Mock lateinit var activityStarter: ActivityStarter
+    @Mock lateinit var activityIntentHelper: ActivityIntentHelper
+    @Mock lateinit var activityTaskManager: IActivityTaskManager
+    @Mock lateinit var cameraIntents: CameraIntentsWrapper
+    @Mock lateinit var contentResolver: ContentResolver
+    @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
+    @Mock lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock lateinit var lockscreenUserManager: NotificationLockscreenUserManager
 
     private lateinit var underTest: CameraGestureHelper
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        whenever(cameraIntents.getSecureCameraIntent(anyInt())).thenReturn(
-            Intent(CameraIntents.DEFAULT_SECURE_CAMERA_INTENT_ACTION)
-        )
-        whenever(cameraIntents.getInsecureCameraIntent(anyInt())).thenReturn(
-            Intent(CameraIntents.DEFAULT_INSECURE_CAMERA_INTENT_ACTION)
-        )
+        whenever(cameraIntents.getSecureCameraIntent(any()))
+            .thenReturn(Intent(CameraIntents.DEFAULT_SECURE_CAMERA_INTENT_ACTION))
+        whenever(cameraIntents.getInsecureCameraIntent(any()))
+            .thenReturn(Intent(CameraIntents.DEFAULT_INSECURE_CAMERA_INTENT_ACTION))
 
         prepare()
 
-        underTest = CameraGestureHelper(
-            context = mock(),
-            centralSurfaces = centralSurfaces,
-            keyguardStateController = keyguardStateController,
-            statusBarKeyguardViewManager = statusBarKeyguardViewManager,
-            packageManager = packageManager,
-            activityManager = activityManager,
-            activityStarter = activityStarter,
-            activityIntentHelper = activityIntentHelper,
-            activityTaskManager = activityTaskManager,
-            cameraIntents = cameraIntents,
-            contentResolver = contentResolver,
-            uiExecutor = MoreExecutors.directExecutor(),
-            selectedUserInteractor = mSelectedUserInteractor,
-        )
+        underTest =
+            CameraGestureHelper(
+                context = mock(),
+                keyguardStateController = keyguardStateController,
+                statusBarKeyguardViewManager = statusBarKeyguardViewManager,
+                packageManager = packageManager,
+                activityManager = activityManager,
+                activityStarter = activityStarter,
+                activityIntentHelper = activityIntentHelper,
+                activityTaskManager = activityTaskManager,
+                cameraIntents = cameraIntents,
+                contentResolver = contentResolver,
+                uiExecutor = MoreExecutors.directExecutor(),
+                selectedUserInteractor = mSelectedUserInteractor,
+                devicePolicyManager = devicePolicyManager,
+                lockscreenUserManager = lockscreenUserManager,
+            )
     }
 
     /**
@@ -116,13 +108,13 @@
      * @param isCameraAllowedByAdmin Whether the device administrator allows use of the camera app
      * @param installedCameraAppCount The number of installed camera apps on the device
      * @param isUsingSecureScreenLockOption Whether the user-controlled setting for Screen Lock is
-     * set with a "secure" option that requires the user to provide some secret/credentials to be
-     * able to unlock the device, for example "Face Unlock", "PIN", or "Password". Examples of
-     * non-secure options are "None" and "Swipe"
+     *   set with a "secure" option that requires the user to provide some secret/credentials to be
+     *   able to unlock the device, for example "Face Unlock", "PIN", or "Password". Examples of
+     *   non-secure options are "None" and "Swipe"
      * @param isCameraActivityRunningOnTop Whether the camera activity is running at the top of the
-     * most recent/current task of activities
+     *   most recent/current task of activities
      * @param isTaskListEmpty Whether there are no active activity tasks at all. Note that this is
-     * treated as `false` if [isCameraActivityRunningOnTop] is set to `true`
+     *   treated as `false` if [isCameraActivityRunningOnTop] is set to `true`
      */
     private fun prepare(
         isCameraAllowedByAdmin: Boolean = true,
@@ -131,7 +123,13 @@
         isCameraActivityRunningOnTop: Boolean = false,
         isTaskListEmpty: Boolean = false,
     ) {
-        whenever(centralSurfaces.isCameraAllowedByAdmin).thenReturn(isCameraAllowedByAdmin)
+        whenever(lockscreenUserManager.getCurrentUserId()).thenReturn(1)
+        if (isCameraAllowedByAdmin) {
+            whenever(devicePolicyManager.getCameraDisabled(isNull(), any())).thenReturn(false)
+            whenever(keyguardStateController.isMethodSecure).thenReturn(false)
+        } else {
+            whenever(devicePolicyManager.getCameraDisabled(isNull(), any())).thenReturn(true)
+        }
 
         whenever(activityIntentHelper.wouldLaunchResolverActivity(any(), anyInt()))
             .thenReturn(installedCameraAppCount > 1)
@@ -141,30 +139,26 @@
             .thenReturn(!isUsingSecureScreenLockOption)
 
         if (installedCameraAppCount >= 1) {
-            val resolveInfo = ResolveInfo().apply {
-                this.activityInfo = ActivityInfo().apply {
-                    packageName = CAMERA_APP_PACKAGE_NAME
+            val resolveInfo =
+                ResolveInfo().apply {
+                    this.activityInfo =
+                        ActivityInfo().apply { packageName = CAMERA_APP_PACKAGE_NAME }
                 }
-            }
-            whenever(packageManager.resolveActivityAsUser(any(), anyInt(), anyInt())).thenReturn(
-                resolveInfo
-            )
+            whenever(packageManager.resolveActivityAsUser(any(), anyInt(), anyInt()))
+                .thenReturn(resolveInfo)
         } else {
-            whenever(packageManager.resolveActivityAsUser(any(), anyInt(), anyInt())).thenReturn(
-                null
-            )
+            whenever(packageManager.resolveActivityAsUser(any(), anyInt(), anyInt()))
+                .thenReturn(null)
         }
 
         when {
             isCameraActivityRunningOnTop -> {
-                val runningTaskInfo = ActivityManager.RunningTaskInfo().apply {
-                    topActivity = ComponentName(CAMERA_APP_PACKAGE_NAME, "cameraActivity")
-                }
-                whenever(activityManager.getRunningTasks(anyInt())).thenReturn(
-                    listOf(
-                        runningTaskInfo
-                    )
-                )
+                val runningTaskInfo =
+                    ActivityManager.RunningTaskInfo().apply {
+                        topActivity = ComponentName(CAMERA_APP_PACKAGE_NAME, "cameraActivity")
+                    }
+                whenever(activityManager.getRunningTasks(anyInt()))
+                    .thenReturn(listOf(runningTaskInfo))
             }
             isTaskListEmpty -> {
                 whenever(activityManager.getRunningTasks(anyInt())).thenReturn(emptyList())
@@ -289,28 +283,28 @@
     ) {
         val intentCaptor = KotlinArgumentCaptor(Intent::class.java)
         if (isSecure && !moreThanOneCameraAppInstalled) {
-            verify(activityTaskManager).startActivityAsUser(
-                any(),
-                any(),
-                any(),
-                intentCaptor.capture(),
-                any(),
-                any(),
-                any(),
-                anyInt(),
-                anyInt(),
-                any(),
-                any(),
-                anyInt()
-            )
+            verify(activityTaskManager)
+                .startActivityAsUser(
+                    isNull(),
+                    isNull(),
+                    isNull(),
+                    intentCaptor.capture(),
+                    isNull(),
+                    isNull(),
+                    isNull(),
+                    anyInt(),
+                    anyInt(),
+                    isNull(),
+                    any(),
+                    anyInt()
+                )
         } else {
             verify(activityStarter).startActivity(intentCaptor.capture(), eq(false))
         }
         val intent = intentCaptor.value
 
         assertThat(CameraIntents.isSecureCameraIntent(intent)).isEqualTo(isSecure)
-        assertThat(intent.getIntExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, -1))
-            .isEqualTo(source)
+        assertThat(intent.getIntExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, -1)).isEqualTo(source)
     }
 
     companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 2bf50b3..91259a6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -58,7 +58,6 @@
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.keyguard.data.repository.BiometricType
 import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.fakeCommandQueue
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -78,7 +77,6 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
-import com.android.systemui.statusbar.commandQueue
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.model.SelectionStatus
@@ -116,7 +114,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
-    private val kosmos = testKosmos().apply { this.commandQueue = this.fakeCommandQueue }
+    private val kosmos = testKosmos()
     private lateinit var underTest: DeviceEntryFaceAuthRepositoryImpl
 
     @Mock private lateinit var faceManager: FaceManager
@@ -162,7 +160,6 @@
     private val displayStateInteractor = kosmos.displayStateInteractor
     private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
     private val displayRepository = kosmos.displayRepository
-    private val fakeCommandQueue = kosmos.fakeCommandQueue
     private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
     private lateinit var featureFlags: FakeFeatureFlags
 
@@ -572,9 +569,7 @@
                 bouncerRepository.setAlternateVisible(false)
                 // Keyguard is occluded when secure camera is active.
                 keyguardRepository.setKeyguardOccluded(true)
-                fakeCommandQueue.doForEachCallback {
-                    it.onCameraLaunchGestureDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
-                }
+                keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
             }
         }
 
@@ -589,9 +584,7 @@
             assertThat(canFaceAuthRun()).isTrue()
 
             // launch secure camera
-            fakeCommandQueue.doForEachCallback {
-                it.onCameraLaunchGestureDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
-            }
+            keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
             keyguardRepository.setKeyguardOccluded(true)
             runCurrent()
             assertThat(canFaceAuthRun()).isFalse()
@@ -870,9 +863,7 @@
                 bouncerRepository.setAlternateVisible(false)
                 // Keyguard is occluded when secure camera is active.
                 keyguardRepository.setKeyguardOccluded(true)
-                fakeCommandQueue.doForEachCallback {
-                    it.onCameraLaunchGestureDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
-                }
+                keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
             }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
index 5115f5a..3b8ffcd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
@@ -173,6 +173,7 @@
         }
 
     @Test
+    @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun transitionToGone_whenOpeningGlanceableHubEditMode() =
         testScope.runTest {
             kosmos.fakeKeyguardBouncerRepository.setAlternateVisible(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 7906a82..fc827a14 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -32,7 +32,7 @@
 import com.android.systemui.keyguard.data.repository.fakeCommandQueue
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
+import com.android.systemui.keyguard.shared.model.CameraLaunchType
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -47,7 +47,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.onCompletion
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -87,31 +86,17 @@
             val cameraLaunchSource = collectLastValue(flow)
             runCurrent()
 
-            commandQueue.doForEachCallback {
-                it.onCameraLaunchGestureDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE)
-            }
-            assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.WIGGLE)
+            underTest.onCameraLaunchDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+            assertThat(cameraLaunchSource()!!.type).isEqualTo(CameraLaunchType.POWER_DOUBLE_TAP)
 
-            commandQueue.doForEachCallback {
-                it.onCameraLaunchGestureDetected(
-                    StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
-                )
-            }
-            assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.POWER_DOUBLE_TAP)
+            underTest.onCameraLaunchDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE)
+            assertThat(cameraLaunchSource()!!.type).isEqualTo(CameraLaunchType.WIGGLE)
 
-            commandQueue.doForEachCallback {
-                it.onCameraLaunchGestureDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER)
-            }
-            assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.LIFT_TRIGGER)
+            underTest.onCameraLaunchDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER)
+            assertThat(cameraLaunchSource()!!.type).isEqualTo(CameraLaunchType.LIFT_TRIGGER)
 
-            commandQueue.doForEachCallback {
-                it.onCameraLaunchGestureDetected(
-                    StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE
-                )
-            }
-            assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.QUICK_AFFORDANCE)
-
-            flow.onCompletion { assertThat(commandQueue.callbackCount()).isEqualTo(0) }
+            underTest.onCameraLaunchDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+            assertThat(cameraLaunchSource()!!.type).isEqualTo(CameraLaunchType.QUICK_AFFORDANCE)
         }
 
     @Test
@@ -121,11 +106,7 @@
             val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
             runCurrent()
 
-            commandQueue.doForEachCallback {
-                it.onCameraLaunchGestureDetected(
-                    StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
-                )
-            }
+            underTest.onCameraLaunchDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
 
             assertThat(secureCameraActive()).isTrue()
 
@@ -146,11 +127,7 @@
             val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
             runCurrent()
 
-            commandQueue.doForEachCallback {
-                it.onCameraLaunchGestureDetected(
-                    StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
-                )
-            }
+            underTest.onCameraLaunchDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
             assertThat(secureCameraActive()).isTrue()
 
             // Keyguard is showing and not occluded
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index d9708a4..9762fd8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import android.app.StatusBarManager
+import android.app.StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
@@ -42,7 +42,6 @@
 import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.fakeCommandQueue
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
@@ -59,7 +58,6 @@
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.shadeTestUtil
-import com.android.systemui.statusbar.commandQueue
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -93,13 +91,12 @@
     private val kosmos =
         testKosmos().apply {
             fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
-            this.commandQueue = fakeCommandQueue
         }
     private val testScope = kosmos.testScope
 
     private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+    private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
     private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
-    private var commandQueue = kosmos.fakeCommandQueue
     private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
     private val transitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
     private lateinit var featureFlags: FakeFeatureFlags
@@ -1724,11 +1721,7 @@
             reset(transitionRepository)
 
             // ...AND WHEN the camera gesture is detected quickly afterwards
-            commandQueue.doForEachCallback {
-                it.onCameraLaunchGestureDetected(
-                    StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
-                )
-            }
+            keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
             runCurrent()
 
             // THEN a transition from DOZING => OCCLUDED should occur
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
similarity index 76%
rename from packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
index 12c9eb9..53dec69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
@@ -21,22 +21,23 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.camera.cameraGestureHelper
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.power.shared.model.WakeSleepReason
-import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
-import kotlin.test.assertEquals
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -47,6 +48,9 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class PowerInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val cameraGestureHelper = kosmos.cameraGestureHelper
 
     private lateinit var underTest: PowerInteractor
     private lateinit var repository: FakePowerRepository
@@ -66,33 +70,30 @@
                 falsingCollector,
                 screenOffAnimationController,
                 statusBarStateController,
+                { cameraGestureHelper },
             )
+
+        whenever(cameraGestureHelper.canCameraGestureBeLaunched(any())).thenReturn(true)
     }
 
     @Test
     fun isInteractive_screenTurnsOff() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             repository.setInteractive(true)
-            var value: Boolean? = null
-            val job = underTest.isInteractive.onEach { value = it }.launchIn(this)
+            val isInteractive by collectLastValue(underTest.isInteractive)
 
             repository.setInteractive(false)
-
-            assertThat(value).isFalse()
-            job.cancel()
+            assertThat(isInteractive).isFalse()
         }
 
     @Test
     fun isInteractive_becomesInteractive() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             repository.setInteractive(false)
-            var value: Boolean? = null
-            val job = underTest.isInteractive.onEach { value = it }.launchIn(this)
+            val isInteractive by collectLastValue(underTest.isInteractive)
 
             repository.setInteractive(true)
-
-            assertThat(value).isTrue()
-            job.cancel()
+            assertThat(isInteractive).isTrue()
         }
 
     @Test
@@ -203,6 +204,23 @@
     }
 
     @Test
+    fun onCameraLaunchGestureDetected_isNotTrueWhenCannotLaunch() {
+        whenever(cameraGestureHelper.canCameraGestureBeLaunched(any())).thenReturn(false)
+        underTest.onStartedWakingUp(
+            PowerManager.WAKE_REASON_POWER_BUTTON,
+            /*powerButtonLaunchGestureTriggeredDuringSleep= */ false
+        )
+        underTest.onFinishedWakingUp()
+        underTest.onCameraLaunchGestureDetected()
+
+        assertThat(repository.wakefulness.value.internalWakefulnessState)
+            .isEqualTo(WakefulnessState.AWAKE)
+        assertThat(repository.wakefulness.value.lastWakeReason)
+            .isEqualTo(WakeSleepReason.POWER_BUTTON)
+        assertFalse(repository.wakefulness.value.powerButtonLaunchGestureTriggered)
+    }
+
+    @Test
     fun onCameraLaunchGestureDetected_maintainsAllOtherState() {
         underTest.onStartedWakingUp(
             PowerManager.WAKE_REASON_POWER_BUTTON,
@@ -211,8 +229,10 @@
         underTest.onFinishedWakingUp()
         underTest.onCameraLaunchGestureDetected()
 
-        assertEquals(WakefulnessState.AWAKE, repository.wakefulness.value.internalWakefulnessState)
-        assertEquals(WakeSleepReason.POWER_BUTTON, repository.wakefulness.value.lastWakeReason)
+        assertThat(repository.wakefulness.value.internalWakefulnessState)
+            .isEqualTo(WakefulnessState.AWAKE)
+        assertThat(repository.wakefulness.value.lastWakeReason)
+            .isEqualTo(WakeSleepReason.POWER_BUTTON)
         assertTrue(repository.wakefulness.value.powerButtonLaunchGestureTriggered)
     }
 
@@ -221,21 +241,23 @@
         underTest.onCameraLaunchGestureDetected()
         // Ensure that the 'false' here does not clear the direct launch detection call earlier.
         // This state should only be reset onStartedGoingToSleep.
-        underTest.onFinishedGoingToSleep(/*powerButtonLaunchGestureTriggeredDuringSleep= */ false)
+        underTest.onFinishedGoingToSleep(/* powerButtonLaunchGestureTriggeredDuringSleep= */ false)
         underTest.onStartedWakingUp(
             PowerManager.WAKE_REASON_POWER_BUTTON,
             /*powerButtonLaunchGestureTriggeredDuringSleep= */ false
         )
         underTest.onFinishedWakingUp()
 
-        assertEquals(WakefulnessState.AWAKE, repository.wakefulness.value.internalWakefulnessState)
-        assertEquals(WakeSleepReason.POWER_BUTTON, repository.wakefulness.value.lastWakeReason)
+        assertThat(repository.wakefulness.value.internalWakefulnessState)
+            .isEqualTo(WakefulnessState.AWAKE)
+        assertThat(repository.wakefulness.value.lastWakeReason)
+            .isEqualTo(WakeSleepReason.POWER_BUTTON)
         assertTrue(repository.wakefulness.value.powerButtonLaunchGestureTriggered)
     }
 
     @Test
     fun cameraLaunchDetectedOnGoingToSleep_stillTrue_ifGestureNotDetectedOnWakingUp() {
-        underTest.onFinishedGoingToSleep(/*powerButtonLaunchGestureTriggeredDuringSleep= */ true)
+        underTest.onFinishedGoingToSleep(/* powerButtonLaunchGestureTriggeredDuringSleep= */ true)
         // Ensure that the 'false' here does not clear the direct launch detection call earlier.
         // This state should only be reset onStartedGoingToSleep.
         underTest.onStartedWakingUp(
@@ -244,12 +266,10 @@
         )
         underTest.onFinishedWakingUp()
 
-        assertEquals(WakefulnessState.AWAKE, repository.wakefulness.value.internalWakefulnessState)
-        assertEquals(WakeSleepReason.POWER_BUTTON, repository.wakefulness.value.lastWakeReason)
+        assertThat(repository.wakefulness.value.internalWakefulnessState)
+            .isEqualTo(WakefulnessState.AWAKE)
+        assertThat(repository.wakefulness.value.lastWakeReason)
+            .isEqualTo(WakeSleepReason.POWER_BUTTON)
         assertTrue(repository.wakefulness.value.powerButtonLaunchGestureTriggered)
     }
-
-    companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
-    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 636d5a7..4a7b887 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -46,6 +46,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
@@ -89,7 +90,7 @@
 @RunWithLooper(setAsMainLooper = true)
 @SmallTest
 public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
-    @Mock private WindowManager mWindowManager;
+    @Mock private ViewCaptureAwareWindowManager mWindowManager;
     @Mock private DozeParameters mDozeParameters;
     @Spy private final NotificationShadeWindowView mNotificationShadeWindowView = spy(
             new NotificationShadeWindowView(mContext, null));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 6f09931..6f1bc7e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -60,7 +60,6 @@
 import com.android.systemui.res.R
 import com.android.systemui.shade.mockLargeScreenHeaderHelper
 import com.android.systemui.shade.shadeTestUtil
-import com.android.systemui.statusbar.notification.NotificationUtils.interpolate
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
@@ -590,6 +589,43 @@
         }
 
     @Test
+    @DisableSceneContainer
+    fun boundsDoNotChangeWhileLockscreenToAodTransitionIsActive() =
+        testScope.runTest {
+            val bounds by collectLastValue(underTest.bounds)
+
+            // Start on lockscreen
+            showLockscreen()
+
+            keyguardInteractor.setNotificationContainerBounds(
+                NotificationContainerBounds(top = 1f, bottom = 1f)
+            )
+            assertThat(bounds).isEqualTo(NotificationContainerBounds(top = 1f, bottom = 1f))
+
+            // Begin transition to AOD
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(LOCKSCREEN, AOD, 0f, TransitionState.STARTED)
+            )
+            runCurrent()
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(LOCKSCREEN, AOD, 0.5f, TransitionState.RUNNING)
+            )
+
+            // Attempt to update bounds
+            keyguardInteractor.setNotificationContainerBounds(
+                NotificationContainerBounds(top = 5f, bottom = 5f)
+            )
+            // Bounds should not have moved
+            assertThat(bounds).isEqualTo(NotificationContainerBounds(top = 1f, bottom = 1f))
+
+            // Transition is over, now move
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(LOCKSCREEN, AOD, 1f, TransitionState.FINISHED)
+            )
+            assertThat(bounds).isEqualTo(NotificationContainerBounds(top = 5f, bottom = 5f))
+        }
+
+    @Test
     @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
     @DisableSceneContainer
     fun boundsOnLockscreenInSplitShade_refactorFlagOff_usesLargeHeaderResource() =
@@ -820,54 +856,6 @@
 
     @Test
     @DisableSceneContainer
-    fun updateBounds_fromKeyguardRoot() =
-        testScope.runTest {
-            val startProgress = 0f
-            val startStep = TransitionStep(LOCKSCREEN, AOD, startProgress, TransitionState.STARTED)
-            val boundsChangingProgress = 0.2f
-            val boundsChangingStep =
-                TransitionStep(LOCKSCREEN, AOD, boundsChangingProgress, TransitionState.RUNNING)
-            val boundsInterpolatingProgress = 0.6f
-            val boundsInterpolatingStep =
-                TransitionStep(
-                    LOCKSCREEN,
-                    AOD,
-                    boundsInterpolatingProgress,
-                    TransitionState.RUNNING
-                )
-            val finishProgress = 1.0f
-            val finishStep =
-                TransitionStep(LOCKSCREEN, AOD, finishProgress, TransitionState.FINISHED)
-
-            val bounds by collectLastValue(underTest.bounds)
-            val top = 123f
-            val bottom = 456f
-
-            kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(startStep)
-            runCurrent()
-            kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(boundsChangingStep)
-            runCurrent()
-            keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom)
-
-            kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(boundsInterpolatingStep)
-            runCurrent()
-            val adjustedProgress =
-                (boundsInterpolatingProgress - boundsChangingProgress) /
-                    (1 - boundsChangingProgress)
-            val interpolatedTop = interpolate(0f, top, adjustedProgress)
-            val interpolatedBottom = interpolate(0f, bottom, adjustedProgress)
-            assertThat(bounds)
-                .isEqualTo(
-                    NotificationContainerBounds(top = interpolatedTop, bottom = interpolatedBottom)
-                )
-
-            kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(finishStep)
-            runCurrent()
-            assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom))
-        }
-
-    @Test
-    @DisableSceneContainer
     fun updateBounds_fromGone_withoutTransitions() =
         testScope.runTest {
             // Start step is already at 1.0
@@ -878,9 +866,9 @@
             val top = 123f
             val bottom = 456f
 
-            kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(runningStep)
+            keyguardTransitionRepository.sendTransitionStep(runningStep)
             runCurrent()
-            kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(finishStep)
+            keyguardTransitionRepository.sendTransitionStep(finishStep)
             runCurrent()
             keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom)
             runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
index 142631e..a1fcfcd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
@@ -16,8 +16,10 @@
 
 package com.android.systemui.volume.domain.interactor
 
+import android.media.AudioManager.STREAM_MUSIC
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
@@ -40,17 +42,57 @@
 
     @Before
     fun setUp() {
-        with(kosmos) { underTest = audioSharingInteractor }
+        with(kosmos) {
+            with(audioSharingRepository) { setVolumeMap(mapOf(TEST_GROUP_ID to TEST_VOLUME)) }
+            underTest = audioSharingInteractor
+        }
     }
 
     @Test
-    fun volumeChanges_returnVolume() {
+    fun handlePrimaryGroupChange_nullVolume() {
         with(kosmos) {
             testScope.runTest {
-                with(audioSharingRepository) {
-                    setSecondaryGroupId(TEST_GROUP_ID)
-                    setVolumeMap(mapOf(TEST_GROUP_ID to TEST_VOLUME))
-                }
+                with(audioSharingRepository) { setPrimaryGroupId(TEST_GROUP_ID_INVALID) }
+                val preMusicStream by
+                    collectLastValue(
+                        audioVolumeInteractor.getAudioStream(AudioStream(STREAM_MUSIC))
+                    )
+                val preVolume = preMusicStream?.volume
+                runCurrent()
+                underTest.handlePrimaryGroupChange()
+                val musicStream by
+                    collectLastValue(
+                        audioVolumeInteractor.getAudioStream(AudioStream(STREAM_MUSIC))
+                    )
+                runCurrent()
+
+                Truth.assertThat(musicStream?.volume).isEqualTo(preVolume)
+            }
+        }
+    }
+
+    @Test
+    fun handlePrimaryGroupChange_setStreamVolume() {
+        with(kosmos) {
+            testScope.runTest {
+                with(audioSharingRepository) { setPrimaryGroupId(TEST_GROUP_ID) }
+                underTest.handlePrimaryGroupChange()
+                val musicStream by
+                    collectLastValue(
+                        audioVolumeInteractor.getAudioStream(AudioStream(STREAM_MUSIC))
+                    )
+                runCurrent()
+
+                Truth.assertThat(musicStream?.volume).isEqualTo(TEST_MUSIC_VOLUME)
+            }
+        }
+    }
+
+    @Test
+    fun secondaryGroupVolumeChanges_returnVolume() {
+        with(kosmos) {
+            testScope.runTest {
+                with(audioSharingRepository) { setSecondaryGroupId(TEST_GROUP_ID) }
                 val volume by collectLastValue(underTest.volume)
                 runCurrent()
 
@@ -60,13 +102,10 @@
     }
 
     @Test
-    fun volumeChanges_returnNull() {
+    fun secondaryGroupVolumeChanges_returnNull() {
         with(kosmos) {
             testScope.runTest {
-                with(audioSharingRepository) {
-                    setSecondaryGroupId(TEST_GROUP_ID_INVALID)
-                    setVolumeMap(mapOf(TEST_GROUP_ID to TEST_VOLUME))
-                }
+                with(audioSharingRepository) { setSecondaryGroupId(TEST_GROUP_ID_INVALID) }
                 val volume by collectLastValue(underTest.volume)
                 runCurrent()
 
@@ -76,7 +115,7 @@
     }
 
     @Test
-    fun volumeChanges_returnDefaultVolume() {
+    fun secondaryGroupVolumeChanges_returnDefaultVolume() {
         with(kosmos) {
             testScope.runTest {
                 with(audioSharingRepository) {
@@ -94,7 +133,8 @@
     private companion object {
         const val TEST_GROUP_ID = 1
         const val TEST_GROUP_ID_INVALID = -1
-        const val TEST_VOLUME = 10
+        const val TEST_MUSIC_VOLUME = 10
+        const val TEST_VOLUME = 255
         const val TEST_VOLUME_DEFAULT = 20
     }
 }
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e56b638..28138a5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3214,9 +3214,6 @@
 
     <!-- Provider Model: Default title of the mobile network in the mobile layout. [CHAR LIMIT=50] -->
     <string name="mobile_data_settings_title">Mobile data</string>
-    <!-- Provider Model: Summary text separator for preferences including a short description
-         (eg. "Connected / 5G"). [CHAR LIMIT=50] -->
-    <string name="preference_summary_default_combination"><xliff:g id="state" example="Connected">%1$s</xliff:g> / <xliff:g id="networkMode" example="LTE">%2$s</xliff:g></string>
     <!-- Provider Model:
          Summary indicating that a SIM has an active mobile data connection [CHAR LIMIT=50] -->
     <string name="mobile_data_connection_active">Connected</string>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 3dd3758..5ffb9ab2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -56,13 +56,13 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.OptIn;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.InstanceId;
@@ -147,7 +147,7 @@
     private final Execution mExecution;
     private final FingerprintManager mFingerprintManager;
     @NonNull private final LayoutInflater mInflater;
-    private final WindowManager mWindowManager;
+    private final ViewCaptureAwareWindowManager mWindowManager;
     private final DelayableExecutor mFgExecutor;
     @NonNull private final Executor mBiometricExecutor;
     @NonNull private final StatusBarStateController mStatusBarStateController;
@@ -693,7 +693,7 @@
             @NonNull Execution execution,
             @NonNull LayoutInflater inflater,
             @Nullable FingerprintManager fingerprintManager,
-            @NonNull WindowManager windowManager,
+            @NonNull ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
             @NonNull StatusBarStateController statusBarStateController,
             @Main DelayableExecutor fgExecutor,
             @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@@ -741,7 +741,7 @@
         // The fingerprint manager is queried for UDFPS before this class is constructed, so the
         // fingerprint manager should never be null.
         mFingerprintManager = checkNotNull(fingerprintManager);
-        mWindowManager = windowManager;
+        mWindowManager = viewCaptureAwareWindowManager;
         mFgExecutor = fgExecutor;
         mStatusBarStateController = statusBarStateController;
         mKeyguardStateController = keyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index e03d160..1bac0bc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -44,6 +44,7 @@
 import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener
 import androidx.annotation.LayoutRes
 import androidx.annotation.VisibleForTesting
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
@@ -94,7 +95,7 @@
 constructor(
     private val context: Context,
     private val inflater: LayoutInflater,
-    private val windowManager: WindowManager,
+    private val windowManager: ViewCaptureAwareWindowManager,
     private val accessibilityManager: AccessibilityManager,
     private val statusBarStateController: StatusBarStateController,
     private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index ecbd3f9..6757edb 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -19,6 +19,7 @@
 import android.app.ActivityManager
 import android.app.ActivityOptions
 import android.app.IActivityTaskManager
+import android.app.admin.DevicePolicyManager
 import android.content.ContentResolver
 import android.content.Context
 import android.content.Intent
@@ -32,8 +33,8 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -45,9 +46,10 @@
  * the camera).
  */
 @SysUISingleton
-class CameraGestureHelper @Inject constructor(
+class CameraGestureHelper
+@Inject
+constructor(
     private val context: Context,
-    private val centralSurfaces: CentralSurfaces,
     private val keyguardStateController: KeyguardStateController,
     private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
     private val packageManager: PackageManager,
@@ -59,24 +61,25 @@
     private val contentResolver: ContentResolver,
     @Main private val uiExecutor: Executor,
     private val selectedUserInteractor: SelectedUserInteractor,
+    private val devicePolicyManager: DevicePolicyManager,
+    private val lockscreenUserManager: NotificationLockscreenUserManager,
 ) {
-    /**
-     * Whether the camera application can be launched for the camera launch gesture.
-     */
+    /** Whether the camera application can be launched for the camera launch gesture. */
     fun canCameraGestureBeLaunched(statusBarState: Int): Boolean {
-        if (!centralSurfaces.isCameraAllowedByAdmin) {
+        if (!isCameraAllowedByAdmin()) {
             return false
         }
 
-        val resolveInfo: ResolveInfo? = packageManager.resolveActivityAsUser(
-            getStartCameraIntent(selectedUserInteractor.getSelectedUserId()),
-            PackageManager.MATCH_DEFAULT_ONLY,
-            selectedUserInteractor.getSelectedUserId()
-        )
+        val resolveInfo: ResolveInfo? =
+            packageManager.resolveActivityAsUser(
+                getStartCameraIntent(selectedUserInteractor.getSelectedUserId()),
+                PackageManager.MATCH_DEFAULT_ONLY,
+                selectedUserInteractor.getSelectedUserId()
+            )
         val resolvedPackage = resolveInfo?.activityInfo?.packageName
         return (resolvedPackage != null &&
-                (statusBarState != StatusBarState.SHADE ||
-                        !activityManager.isInForeground(resolvedPackage)))
+            (statusBarState != StatusBarState.SHADE ||
+                !activityManager.isInForeground(resolvedPackage)))
     }
 
     /**
@@ -87,9 +90,11 @@
     fun launchCamera(source: Int) {
         val intent: Intent = getStartCameraIntent(selectedUserInteractor.getSelectedUserId())
         intent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source)
-        val wouldLaunchResolverActivity = activityIntentHelper.wouldLaunchResolverActivity(
-            intent, selectedUserInteractor.getSelectedUserId()
-        )
+        val wouldLaunchResolverActivity =
+            activityIntentHelper.wouldLaunchResolverActivity(
+                intent,
+                selectedUserInteractor.getSelectedUserId()
+            )
         if (CameraIntents.isSecureCameraIntent(intent) && !wouldLaunchResolverActivity) {
             uiExecutor.execute {
                 // Normally an activity will set its requested rotation animation on its window.
@@ -101,7 +106,7 @@
                 val activityOptions = ActivityOptions.makeBasic()
                 activityOptions.setDisallowEnterPictureInPictureWhileLaunching(true)
                 activityOptions.rotationAnimationHint =
-                        WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
+                    WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
                 try {
                     activityTaskManager.startActivityAsUser(
                         null,
@@ -118,11 +123,7 @@
                         selectedUserInteractor.getSelectedUserId(true),
                     )
                 } catch (e: RemoteException) {
-                    Log.w(
-                        "CameraGestureHelper",
-                        "Unable to start camera activity",
-                        e
-                    )
+                    Log.w("CameraGestureHelper", "Unable to start camera activity", e)
                 }
             }
         } else {
@@ -131,9 +132,6 @@
             activityStarter.startActivity(intent, false /* dismissShade */)
         }
 
-        // Call this to make sure that the keyguard returns if the app that is being launched
-        // crashes after a timeout.
-        centralSurfaces.startLaunchTransitionTimeout()
         // Call this to make sure the keyguard is ready to be dismissed once the next intent is
         // handled by the OS (in our case it is the activity we started right above)
         statusBarKeyguardViewManager.readyForKeyguardDone()
@@ -152,4 +150,17 @@
             cameraIntents.getInsecureCameraIntent(userId)
         }
     }
+
+    private fun isCameraAllowedByAdmin(): Boolean {
+        if (devicePolicyManager.getCameraDisabled(null, lockscreenUserManager.getCurrentUserId())) {
+            return false
+        } else if (keyguardStateController.isShowing() && statusBarKeyguardViewManager.isSecure()) {
+            // Check if the admin has disabled the camera specifically for the keyguard
+            return (devicePolicyManager.getKeyguardDisabledFeatures(
+                null,
+                lockscreenUserManager.getCurrentUserId()
+            ) and DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) == 0
+        }
+        return true
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt
index f649be2..b859cdc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt
@@ -20,6 +20,7 @@
 import android.graphics.Paint
 import android.graphics.PixelFormat
 import android.view.WindowManager
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyboard.docking.ui.KeyboardDockingIndicationView
@@ -37,7 +38,7 @@
     context: Context,
     @Application private val applicationScope: CoroutineScope,
     private val viewModel: KeyboardDockingIndicationViewModel,
-    private val windowManager: WindowManager
+    private val windowManager: ViewCaptureAwareWindowManager,
 ) {
 
     private val windowLayoutParams =
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 ae751db..edf17c1 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
@@ -34,6 +34,7 @@
 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
 import com.android.systemui.keyguard.shared.model.DismissAction
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
@@ -237,6 +238,9 @@
     /** Observable updated when keyguardDone should be called either now or soon. */
     val keyguardDone: Flow<KeyguardDone>
 
+    /** Last camera launch detection event */
+    val onCameraLaunchDetected: MutableStateFlow<CameraLaunchSourceModel>
+
     /**
      * Emits after the keyguard is done animating away.
      *
@@ -380,6 +384,8 @@
     private val _keyguardAlpha = MutableStateFlow(1f)
     override val keyguardAlpha = _keyguardAlpha.asStateFlow()
 
+    override val onCameraLaunchDetected = MutableStateFlow(CameraLaunchSourceModel())
+
     override val panelAlpha: MutableStateFlow<Float> = MutableStateFlow(1f)
 
     private val _clockShouldBeCentered = MutableStateFlow(true)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 046e79c..42490c4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -23,10 +23,7 @@
 import android.graphics.Point
 import android.util.MathUtils
 import com.android.app.animation.Interpolators
-import com.android.app.tracing.FlowTracing.tracedAwaitClose
-import com.android.app.tracing.FlowTracing.tracedConflatedCallbackFlow
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
@@ -35,9 +32,11 @@
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
+import com.android.systemui.keyguard.shared.model.CameraLaunchType
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
@@ -48,11 +47,8 @@
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.ShadeRepository
-import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.notification.NotificationUtils.interpolate
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
-import com.android.systemui.util.kotlin.pairwise
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import javax.inject.Provider
@@ -69,9 +65,7 @@
 import kotlinx.coroutines.flow.debounce
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
@@ -87,7 +81,6 @@
 @Inject
 constructor(
     private val repository: KeyguardRepository,
-    private val commandQueue: CommandQueue,
     powerInteractor: PowerInteractor,
     bouncerRepository: KeyguardBouncerRepository,
     configurationInteractor: ConfigurationInteractor,
@@ -102,55 +95,34 @@
     // TODO(b/296118689): move to a repository
     private val _notificationPlaceholderBounds = MutableStateFlow(NotificationContainerBounds())
 
-    // When going to AOD, we interpolate bounds when receiving the new bounds
-    // When going back to LS, we'll apply new bounds directly
-    private val _nonSplitShadeNotifciationPlaceholderBounds =
-        _notificationPlaceholderBounds.pairwise().flatMapLatest { (oldBounds, newBounds) ->
-            val lastChangeStep = keyguardTransitionInteractor.transitionState.first()
-            if (lastChangeStep.to == AOD) {
-                keyguardTransitionInteractor.transitionState.map { step ->
-                    val startingProgress = lastChangeStep.value
-                    val progress = step.value
-                    if (step.to == AOD && progress >= startingProgress && startingProgress < 1f) {
-                        val adjustedProgress =
-                            ((progress - startingProgress) / (1F - startingProgress)).coerceIn(
-                                0F,
-                                1F
-                            )
-                        val top = interpolate(oldBounds.top, newBounds.top, adjustedProgress)
-                        val bottom =
-                            interpolate(
-                                oldBounds.bottom,
-                                newBounds.bottom,
-                                adjustedProgress.coerceIn(0F, 1F)
-                            )
-                        NotificationContainerBounds(top = top, bottom = bottom)
-                    } else {
-                        newBounds
-                    }
-                }
-            } else {
-                flow { emit(newBounds) }
-            }
-        }
-
     /** Bounds of the notification container. */
     val notificationContainerBounds: StateFlow<NotificationContainerBounds> by lazy {
         SceneContainerFlag.assertInLegacyMode()
-        combine(
+        combineTransform(
                 _notificationPlaceholderBounds,
-                _nonSplitShadeNotifciationPlaceholderBounds,
                 sharedNotificationContainerInteractor.get().configurationBasedDimensions,
-            ) { bounds, nonSplitShadeBounds: NotificationContainerBounds, cfg ->
+                keyguardTransitionInteractor.isInTransition(
+                    edge = Edge.create(from = LOCKSCREEN, to = AOD)
+                ),
+            ) { bounds, cfg, isTransitioningToAod ->
+                if (isTransitioningToAod) {
+                    // Keep bounds stable during this transition, to prevent cases like smartspace
+                    // popping in and adjusting the bounds. A prime example would be media playing,
+                    // which then updates smartspace on transition to AOD
+                    return@combineTransform
+                }
+
                 // We offset the placeholder bounds by the configured top margin to account for
                 // legacy placement behavior within notifications for splitshade.
-                if (MigrateClocksToBlueprint.isEnabled) {
-                    if (cfg.useSplitShade) {
-                        bounds.copy(bottom = bounds.bottom - cfg.keyguardSplitShadeTopMargin)
-                    } else {
-                        nonSplitShadeBounds
-                    }
-                } else bounds
+                emit(
+                    if (MigrateClocksToBlueprint.isEnabled) {
+                        if (cfg.useSplitShade) {
+                            bounds.copy(bottom = bounds.bottom - cfg.keyguardSplitShadeTopMargin)
+                        } else {
+                            bounds
+                        }
+                    } else bounds
+                )
             }
             .stateIn(
                 scope = applicationScope,
@@ -198,22 +170,7 @@
 
     /** Event for when the camera gesture is detected */
     val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> =
-        tracedConflatedCallbackFlow("KeyguardInteractor#onCameraLaunchDetected") {
-            val callback =
-                object : CommandQueue.Callbacks {
-                    override fun onCameraLaunchGestureDetected(source: Int) {
-                        trySendWithFailureLogging(
-                            cameraLaunchSourceIntToModel(source),
-                            TAG,
-                            "updated onCameraLaunchGestureDetected"
-                        )
-                    }
-                }
-
-            commandQueue.addCallback(callback)
-
-            tracedAwaitClose("onCameraLaunchDetected") { commandQueue.removeCallback(callback) }
-        }
+        repository.onCameraLaunchDetected.filter { it.type != CameraLaunchType.IGNORE }
 
     /**
      * Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
@@ -310,7 +267,7 @@
                 when {
                     isKeyguardVisible -> false
                     isPrimaryBouncerShowing -> false
-                    else -> cameraLaunchEvent == CameraLaunchSourceModel.POWER_DOUBLE_TAP
+                    else -> cameraLaunchEvent.type == CameraLaunchType.POWER_DOUBLE_TAP
                 }
             }
             .onStart { emit(false) }
@@ -440,16 +397,15 @@
         return repository.isKeyguardShowing()
     }
 
-    private fun cameraLaunchSourceIntToModel(value: Int): CameraLaunchSourceModel {
+    private fun cameraLaunchSourceIntToType(value: Int): CameraLaunchType {
         return when (value) {
-            StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE -> CameraLaunchSourceModel.WIGGLE
+            StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE -> CameraLaunchType.WIGGLE
             StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP ->
-                CameraLaunchSourceModel.POWER_DOUBLE_TAP
-            StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER ->
-                CameraLaunchSourceModel.LIFT_TRIGGER
+                CameraLaunchType.POWER_DOUBLE_TAP
+            StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER -> CameraLaunchType.LIFT_TRIGGER
             StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE ->
-                CameraLaunchSourceModel.QUICK_AFFORDANCE
-            else -> throw IllegalArgumentException("Invalid CameraLaunchSourceModel value: $value")
+                CameraLaunchType.QUICK_AFFORDANCE
+            else -> throw IllegalArgumentException("Invalid CameraLaunchType value: $value")
         }
     }
 
@@ -508,6 +464,11 @@
         fromLockscreenTransitionInteractor.get().dismissKeyguard()
     }
 
+    fun onCameraLaunchDetected(source: Int) {
+        repository.onCameraLaunchDetected.value =
+            CameraLaunchSourceModel(type = cameraLaunchSourceIntToType(source))
+    }
+
     companion object {
         private const val TAG = "KeyguardInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt
index 19baf77..c017651 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt
@@ -15,14 +15,8 @@
  */
 package com.android.systemui.keyguard.shared.model
 
-/** Camera launch sources */
-enum class CameraLaunchSourceModel {
-    /** Device is wiggled */
-    WIGGLE,
-    /** Power button has been double tapped */
-    POWER_DOUBLE_TAP,
-    /** Device has been lifted */
-    LIFT_TRIGGER,
-    /** Quick affordance button has been pressed */
-    QUICK_AFFORDANCE,
-}
+/** Camera launch source, with type and time detected */
+data class CameraLaunchSourceModel(
+    val type: CameraLaunchType = CameraLaunchType.IGNORE,
+    val detectedTime: Long = System.currentTimeMillis(),
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchType.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchType.kt
new file mode 100644
index 0000000..984abbb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchType.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.shared.model
+
+/** Camera launch sources */
+enum class CameraLaunchType {
+    /** Models no value */
+    IGNORE,
+    /** Device is wiggled */
+    WIGGLE,
+    /** Power button has been double tapped */
+    POWER_DOUBLE_TAP,
+    /** Device has been lifted */
+    LIFT_TRIGGER,
+    /** Quick affordance button has been pressed */
+    QUICK_AFFORDANCE,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index 9380d44..8d48c1d 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.power.domain.interactor
 
 import android.os.PowerManager
+import com.android.systemui.camera.CameraGestureHelper
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorActual
 import com.android.systemui.dagger.SysUISingleton
@@ -28,6 +29,7 @@
 import com.android.systemui.power.shared.model.WakefulnessState
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import javax.inject.Inject
+import javax.inject.Provider
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
@@ -41,6 +43,7 @@
     @FalsingCollectorActual private val falsingCollector: FalsingCollector,
     private val screenOffAnimationController: ScreenOffAnimationController,
     private val statusBarStateController: StatusBarStateController,
+    private val cameraGestureHelper: Provider<CameraGestureHelper>,
 ) {
     /** Whether the screen is on or off. */
     val isInteractive: Flow<Boolean> = repository.isInteractive
@@ -206,7 +209,13 @@
     }
 
     fun onCameraLaunchGestureDetected() {
-        repository.updateWakefulness(powerButtonLaunchGestureTriggered = true)
+        if (
+            cameraGestureHelper
+                .get()
+                .canCameraGestureBeLaunched(statusBarStateController.getState())
+        ) {
+            repository.updateWakefulness(powerButtonLaunchGestureTriggered = true)
+        }
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
index 5432793d..0f49c94 100644
--- a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
@@ -19,7 +19,7 @@
      * all use cases. If you need more granular information about a waking/sleeping transition, use
      * the [KeyguardTransitionInteractor].
      */
-    internal val internalWakefulnessState: WakefulnessState = WakefulnessState.AWAKE,
+    val internalWakefulnessState: WakefulnessState = WakefulnessState.AWAKE,
     val lastWakeReason: WakeSleepReason = WakeSleepReason.OTHER,
     val lastSleepReason: WakeSleepReason = WakeSleepReason.OTHER,
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index dbfe818..abc0453 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -586,6 +586,15 @@
                     )
                 )
             )
+        } else {
+            if (isLongClickable) {
+                info.addAction(
+                    AccessibilityNodeInfo.AccessibilityAction(
+                        AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id,
+                        resources.getString(R.string.accessibility_long_click_tile)
+                    )
+                )
+            }
         }
         if (!TextUtils.isEmpty(accessibilityClass)) {
             info.className =
@@ -597,14 +606,6 @@
             if (Switch::class.java.name == accessibilityClass) {
                 info.isChecked = tileState
                 info.isCheckable = true
-                if (isLongClickable) {
-                    info.addAction(
-                        AccessibilityNodeInfo.AccessibilityAction(
-                            AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id,
-                            resources.getString(R.string.accessibility_long_click_tile)
-                        )
-                    )
-                }
             }
         }
         if (position != INVALID) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 158eb6e..b2873c5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -736,7 +736,8 @@
         // Set network description for the carrier network when connecting to the carrier network
         // under the airplane mode ON.
         if (activeNetworkIsCellular() || isCarrierNetworkActive()) {
-            summary = context.getString(R.string.preference_summary_default_combination,
+            summary = context.getString(
+                    com.android.settingslib.R.string.preference_summary_default_combination,
                     context.getString(
                             isForDds // if nonDds is active, explains Dds status as poor connection
                                     ? (isOnNonDds ? R.string.mobile_data_poor_connection
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index ba0a8d6..2cdcc24 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -19,7 +19,6 @@
 import android.content.Context
 import android.os.UserHandle
 import android.util.Log
-import androidx.annotation.GuardedBy
 import com.android.internal.logging.InstanceId
 import com.android.systemui.Dumpable
 import com.android.systemui.animation.Expandable
@@ -34,6 +33,7 @@
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import java.io.PrintWriter
+import java.util.concurrent.CopyOnWriteArraySet
 import java.util.function.Supplier
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
@@ -57,10 +57,8 @@
     private val context
         get() = qsHost.context
 
-    @GuardedBy("callbacks")
-    private val callbacks: MutableCollection<QSTile.Callback> = mutableSetOf()
-    @GuardedBy("listeningClients")
-    private val listeningClients: MutableCollection<Any> = mutableSetOf()
+    private val callbacks = CopyOnWriteArraySet<QSTile.Callback>()
+    private val listeningClients = CopyOnWriteArraySet<Any>()
 
     // Cancels the jobs when the adapter is no longer alive
     private var tileAdapterJob: Job? = null
@@ -113,19 +111,17 @@
 
     override fun addCallback(callback: QSTile.Callback?) {
         callback ?: return
-        synchronized(callbacks) {
-            callbacks.add(callback)
-            state?.let(callback::onStateChanged)
-        }
+        callbacks.add(callback)
+        state?.let(callback::onStateChanged)
     }
 
     override fun removeCallback(callback: QSTile.Callback?) {
         callback ?: return
-        synchronized(callbacks) { callbacks.remove(callback) }
+        callbacks.remove(callback)
     }
 
     override fun removeCallbacks() {
-        synchronized(callbacks) { callbacks.clear() }
+        callbacks.clear()
     }
 
     override fun click(expandable: Expandable?) {
@@ -163,32 +159,28 @@
 
     override fun setListening(client: Any?, listening: Boolean) {
         client ?: return
-        synchronized(listeningClients) {
-            if (listening) {
-                listeningClients.add(client)
-                if (listeningClients.size == 1) {
-                    stateJob =
-                        qsTileViewModel.state
-                            .filterNotNull()
-                            .map { mapState(context, it, qsTileViewModel.config) }
-                            .onEach { legacyState ->
-                                synchronized(callbacks) {
-                                    callbacks.forEach { it.onStateChanged(legacyState) }
-                                }
-                            }
-                            .launchIn(applicationScope)
-                }
-            } else {
-                listeningClients.remove(client)
-                if (listeningClients.isEmpty()) {
-                    stateJob?.cancel()
-                }
+        if (listening) {
+            listeningClients.add(client)
+            if (listeningClients.size == 1) {
+                stateJob =
+                    qsTileViewModel.state
+                        .filterNotNull()
+                        .map { mapState(context, it, qsTileViewModel.config) }
+                        .onEach { legacyState ->
+                            callbacks.forEach { it.onStateChanged(legacyState) }
+                        }
+                        .launchIn(applicationScope)
+            }
+        } else {
+            listeningClients.remove(client)
+            if (listeningClients.isEmpty()) {
+                stateJob?.cancel()
             }
         }
     }
 
     override fun isListening(): Boolean =
-        synchronized(listeningClients) { listeningClients.isNotEmpty() }
+        listeningClients.isNotEmpty()
 
     override fun setDetailListening(show: Boolean) {
         // do nothing like QSTileImpl
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index bc5cf2a..b60c193 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -41,10 +41,10 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
-import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 import android.view.WindowManagerGlobal;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
 import com.android.systemui.biometrics.AuthController;
@@ -101,7 +101,7 @@
 
     private final Context mContext;
     private final WindowRootViewComponent.Factory mWindowRootViewComponentFactory;
-    private final WindowManager mWindowManager;
+    private final ViewCaptureAwareWindowManager mWindowManager;
     private final IActivityManager mActivityManager;
     private final DozeParameters mDozeParameters;
     private final KeyguardStateController mKeyguardStateController;
@@ -145,7 +145,7 @@
     public NotificationShadeWindowControllerImpl(
             Context context,
             WindowRootViewComponent.Factory windowRootViewComponentFactory,
-            WindowManager windowManager,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
             IActivityManager activityManager,
             DozeParameters dozeParameters,
             StatusBarStateController statusBarStateController,
@@ -165,7 +165,7 @@
             Lazy<CommunalInteractor> communalInteractor) {
         mContext = context;
         mWindowRootViewComponentFactory = windowRootViewComponentFactory;
-        mWindowManager = windowManager;
+        mWindowManager = viewCaptureAwareWindowManager;
         mActivityManager = activityManager;
         mDozeParameters = dozeParameters;
         mKeyguardStateController = keyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 1a7bc16..e8a7840 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -91,6 +91,12 @@
     val expandFraction: Flow<Float> = shadeInteractor.anyExpansion.dumpValue("expandFraction")
 
     /**
+     * The amount [0-1] that quick settings has been opened. At 0, the shade may be open or closed;
+     * at 1, the quick settings are open.
+     */
+    val shadeToQsFraction: Flow<Float> = shadeInteractor.qsExpansion.dumpValue("shadeToQsFraction")
+
+    /**
      * The amount in px that the notification stack should scroll due to internal expansion. This
      * should only happen when a notification expansion hits the bottom of the screen, so it is
      * necessary to scroll up to keep expanding the notification.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index a0d4ca2..ae31151 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -261,8 +261,6 @@
 
     boolean isScreenFullyOff();
 
-    boolean isCameraAllowedByAdmin();
-
     boolean isGoingToSleep();
 
     void notifyBiometricAuthModeChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index c8a4450..5209d0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -49,6 +49,7 @@
 import com.android.systemui.emergency.EmergencyGesture;
 import com.android.systemui.emergency.EmergencyGestureModule.EmergencyGestureIntentFactory;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QSPanelController;
@@ -109,6 +110,7 @@
     private final Lazy<CameraLauncher> mCameraLauncherLazy;
     private final QuickSettingsController mQsController;
     private final QSHost mQSHost;
+    private final KeyguardInteractor mKeyguardInteractor;
     private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
 
@@ -148,6 +150,7 @@
             UserTracker userTracker,
             QSHost qsHost,
             ActivityStarter activityStarter,
+            KeyguardInteractor keyguardInteractor,
             EmergencyGestureIntentFactory emergencyGestureIntentFactory) {
         mCentralSurfaces = centralSurfaces;
         mQsController = quickSettingsController;
@@ -176,7 +179,7 @@
         mCameraLauncherLazy = cameraLauncherLazy;
         mUserTracker = userTracker;
         mQSHost = qsHost;
-
+        mKeyguardInteractor = keyguardInteractor;
         mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
         mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
                 mVibratorOptional, resources);
@@ -351,6 +354,8 @@
             }
             return;
         }
+        mKeyguardInteractor.onCameraLaunchDetected(source);
+
         if (!mCentralSurfaces.isDeviceInteractive()) {
             mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_CAMERA_LAUNCH,
                     "com.android.systemui:CAMERA_GESTURE");
@@ -383,6 +388,7 @@
                 if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
                     mStatusBarKeyguardViewManager.reset(true /* hide */);
                 }
+                mCentralSurfaces.startLaunchTransitionTimeout();
                 mCameraLauncherLazy.get().launchCamera(source,
                         mPanelExpansionInteractor.isFullyCollapsed());
                 mCentralSurfaces.updateScrimController();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
index 88d3e07..d4f2a93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -34,72 +34,127 @@
  */
 abstract class CentralSurfacesEmptyImpl : CentralSurfaces {
     override val lifecycle = LifecycleRegistry(this)
+
     override fun updateIsKeyguard() = false
+
     override fun updateIsKeyguard(forceStateChange: Boolean) = false
+
     override fun getKeyguardMessageArea(): AuthKeyguardMessageArea? = null
+
     override fun isLaunchingActivityOverLockscreen() = false
+
     override fun isDismissingShadeForActivityLaunch() = false
+
     override fun onKeyguardViewManagerStatesUpdated() {}
+
     override fun getCommandQueuePanelsEnabled() = false
+
     override fun showWirelessChargingAnimation(batteryLevel: Int) {}
+
     override fun checkBarModes() {}
+
     override fun updateBubblesVisibility() {}
+
     override fun setInteracting(barWindow: Int, interacting: Boolean) {}
+
     override fun getDisplayWidth() = 0f
+
     override fun getDisplayHeight() = 0f
+
     override fun showKeyguard() {}
+
     override fun hideKeyguard() = false
+
     override fun showKeyguardImpl() {}
+
     override fun fadeKeyguardAfterLaunchTransition(
         beforeFading: Runnable?,
         endRunnable: Runnable?,
         cancelRunnable: Runnable?,
     ) {}
+
     override fun startLaunchTransitionTimeout() {}
+
     override fun hideKeyguardImpl(forceStateChange: Boolean) = false
+
     override fun keyguardGoingAway() {}
+
     override fun setKeyguardFadingAway(startTime: Long, delay: Long, fadeoutDuration: Long) {}
+
     override fun finishKeyguardFadingAway() {}
+
     override fun userActivity() {}
+
     override fun endAffordanceLaunch() {}
+
     override fun shouldKeyguardHideImmediately() = false
+
     override fun showBouncerWithDimissAndCancelIfKeyguard(
         performAction: OnDismissAction?,
         cancelAction: Runnable?,
     ) {}
+
     override fun getNavigationBarView(): NavigationBarView? = null
+
     override fun setBouncerShowing(bouncerShowing: Boolean) {}
+
     override fun isScreenFullyOff() = false
-    override fun isCameraAllowedByAdmin() = false
+
     override fun isGoingToSleep() = false
+
     override fun notifyBiometricAuthModeChanged() {}
+
     override fun setTransitionToFullShadeProgress(transitionToFullShadeProgress: Float) {}
+
     override fun setPrimaryBouncerHiddenFraction(expansion: Float) {}
+
     override fun updateScrimController() {}
+
     override fun shouldIgnoreTouch() = false
+
     override fun isDeviceInteractive() = false
+
     override fun handleExternalShadeWindowTouch(event: MotionEvent?) {}
+
     override fun handleCommunalHubTouch(event: MotionEvent?) {}
+
     override fun awakenDreams() {}
+
     override fun isBouncerShowing() = false
+
     override fun isBouncerShowingScrimmed() = false
+
     override fun updateNotificationPanelTouchState() {}
+
     override fun getRotation() = 0
+
     override fun setBarStateForTest(state: Int) {}
+
     override fun acquireGestureWakeLock(time: Long) {}
+
     override fun resendMessage(msg: Int) {}
+
     override fun resendMessage(msg: Any?) {}
+
     override fun setLastCameraLaunchSource(source: Int) {}
+
     override fun setLaunchCameraOnFinishedGoingToSleep(launch: Boolean) {}
+
     override fun setLaunchCameraOnFinishedWaking(launch: Boolean) {}
+
     override fun setLaunchEmergencyActionOnFinishedGoingToSleep(launch: Boolean) {}
+
     override fun setLaunchEmergencyActionOnFinishedWaking(launch: Boolean) {}
+
     override fun getQSPanelController(): QSPanelController? = null
+
     override fun getDisplayDensity() = 0f
+
     override fun setIsLaunchingActivityOverLockscreen(
         isLaunchingActivityOverLockscreen: Boolean,
         dismissShade: Boolean,
     ) {}
+
     override fun getAnimatorControllerFromNotification(
         associatedView: ExpandableNotificationRow?,
     ): ActivityTransitionAnimator.Controller? = null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 462ae7a..461a38d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -47,7 +47,6 @@
 import android.app.TaskInfo;
 import android.app.UiModeManager;
 import android.app.WallpaperManager;
-import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -883,8 +882,6 @@
 
         // start old BaseStatusBar.start().
         mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
-        mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
-                Context.DEVICE_POLICY_SERVICE);
 
         mAccessibilityManager = (AccessibilityManager)
                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
@@ -2627,6 +2624,7 @@
                 mStackScrollerController.updateSensitivenessForOccludedWakeup();
             }
             if (mLaunchCameraWhenFinishedWaking) {
+                startLaunchTransitionTimeout();
                 mCameraLauncherLazy.get().launchCamera(mLastCameraLaunchSource,
                         mShadeSurface.isFullyCollapsed());
                 mLaunchCameraWhenFinishedWaking = false;
@@ -2701,21 +2699,6 @@
     }
 
     @Override
-    public boolean isCameraAllowedByAdmin() {
-        if (mDevicePolicyManager.getCameraDisabled(null,
-                mLockscreenUserManager.getCurrentUserId())) {
-            return false;
-        } else if (mKeyguardStateController.isShowing()
-                && mStatusBarKeyguardViewManager.isSecure()) {
-            // Check if the admin has disabled the camera specifically for the keyguard
-            return (mDevicePolicyManager.getKeyguardDisabledFeatures(null,
-                    mLockscreenUserManager.getCurrentUserId())
-                    & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) == 0;
-        }
-        return true;
-    }
-
-    @Override
     public boolean isGoingToSleep() {
         return mWakefulnessLifecycle.getWakefulness()
                 == WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP;
@@ -2864,7 +2847,6 @@
 
     protected boolean mDeviceInteractive;
 
-    protected DevicePolicyManager mDevicePolicyManager;
     private final PowerManager mPowerManager;
     protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index e08dbb9..b2035e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -389,10 +389,13 @@
     //  OnReorderingAllowedListener:
 
     private final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> {
-        mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
         if (NotificationThrottleHun.isEnabled()) {
             mAvalancheController.setEnableAtRuntime(true);
+            if (mEntriesToRemoveWhenReorderingAllowed.isEmpty()) {
+                return;
+            }
         }
+        mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
         for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
             if (isHeadsUpEntry(entry.getKey())) {
                 // Maybe the heads-up was removed already
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index a963826..5ba5c06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -54,6 +54,7 @@
                 // allowed again.
                 logDroppedHunsInBackground(getWaitingKeys().size)
                 clearNext()
+                headsUpEntryShowing = null
             }
             if (field != value) {
                 field = value
@@ -222,15 +223,19 @@
 
     /**
      * Returns duration based on
-     * 1) Whether HeadsUpEntry is the last one tracked byAvalancheController
+     * 1) Whether HeadsUpEntry is the last one tracked by AvalancheController
      * 2) The priority of the top HUN in the next batch Used by
      *    BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration.
      */
-    fun getDurationMs(entry: HeadsUpEntry, autoDismissMs: Int): Int {
+    fun getDurationMs(entry: HeadsUpEntry?, autoDismissMs: Int): Int {
         if (!isEnabled()) {
             // Use default duration, like we did before AvalancheController existed
             return autoDismissMs
         }
+        if (entry == null) {
+            // This should never happen
+            return autoDismissMs
+        }
         val showingList: MutableList<HeadsUpEntry> = mutableListOf()
         if (headsUpEntryShowing != null) {
             showingList.add(headsUpEntryShowing!!)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index dcd9cae..e23f6ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -344,6 +344,15 @@
     }
 
     protected boolean hasFullScreenIntent(@NonNull NotificationEntry entry) {
+        if (entry == null) {
+            return false;
+        }
+        if (entry.getSbn() == null) {
+            return false;
+        }
+        if (entry.getSbn().getNotification() == null) {
+            return false;
+        }
         return entry.getSbn().getNotification().fullScreenIntent != null;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningAction.kt b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningAction.kt
new file mode 100644
index 0000000..a77acb5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningAction.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.volume
+
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_IMMUTABLE
+import android.app.PendingIntent.FLAG_UPDATE_CURRENT
+import android.content.Context
+import android.content.Intent
+
+/**
+ * label: Notification action label text. intent: The Intent used to start Activity or Broadcast.
+ * isActivity: Defines if the pending intent should start an activity. Default is to broadcast
+ */
+data class CsdWarningAction(
+    val label: String? = null,
+    val intent: Intent? = null,
+    val isActivity: Boolean = false,
+) {
+    fun toPendingIntent(context: Context): PendingIntent? {
+        if (label == null || intent == null) {
+            return null
+        }
+        if (isActivity) {
+            return PendingIntent.getActivity(
+                context,
+                0,
+                intent,
+                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+            )
+        }
+        return PendingIntent.getBroadcast(context, 0, intent, FLAG_IMMUTABLE)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
index bb230e6..a63660b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
@@ -30,7 +30,6 @@
 import android.media.AudioManager;
 import android.provider.Settings;
 import android.util.Log;
-import android.util.Pair;
 import android.view.KeyEvent;
 import android.view.WindowManager;
 
@@ -109,7 +108,7 @@
     private long mShowTime;
 
     @VisibleForTesting public int mCachedMediaStreamVolume;
-    private Optional<ImmutableList<Pair<String, Intent>>> mActionIntents;
+    private Optional<ImmutableList<CsdWarningAction>> mActionIntents;
     private final BroadcastDispatcher mBroadcastDispatcher;
 
     /**
@@ -121,7 +120,7 @@
         CsdWarningDialog create(
                 int csdWarning,
                 Runnable onCleanup,
-                Optional<ImmutableList<Pair<String, Intent>>> actionIntents);
+                Optional<ImmutableList<CsdWarningAction>> actionIntents);
     }
 
     @AssistedInject
@@ -132,7 +131,7 @@
             NotificationManager notificationManager,
             @Background DelayableExecutor delayableExecutor,
             @Assisted Runnable onCleanup,
-            @Assisted Optional<ImmutableList<Pair<String, Intent>>> actionIntents,
+            @Assisted Optional<ImmutableList<CsdWarningAction>> actionIntents,
             BroadcastDispatcher broadcastDispatcher) {
         super(context);
         mCsdWarning = csdWarning;
@@ -351,39 +350,45 @@
         if (Flags.sounddoseCustomization()
                 && mActionIntents.isPresent()
                 && !mActionIntents.get().isEmpty()) {
-            ImmutableList<Pair<String, Intent>> actionIntentsList = mActionIntents.get();
-            for (Pair<String, Intent> intentPair : actionIntentsList) {
-                if (intentPair != null && intentPair.first != null && intentPair.second != null) {
-                    PendingIntent pendingActionIntent =
-                            PendingIntent.getBroadcast(mContext, 0, intentPair.second,
-                                    FLAG_IMMUTABLE);
-                    builder.addAction(0, intentPair.first, pendingActionIntent);
-                    // Register receiver to undo volume only when
-                    // notification conaining the undo action would be sent.
-                    if (intentPair.first == mContext.getString(R.string.volume_undo_action)) {
-                        final IntentFilter filterUndo = new IntentFilter(
-                                VolumeDialog.ACTION_VOLUME_UNDO);
-                        mBroadcastDispatcher.registerReceiver(mReceiverUndo,
-                                filterUndo,
-                                /* executor = default */ null,
-                                /* user = default */ null,
-                                Context.RECEIVER_NOT_EXPORTED,
-                                /* permission = default */ null);
+            ImmutableList<CsdWarningAction> actionIntentsList = mActionIntents.get();
+            for (CsdWarningAction action : actionIntentsList) {
+                if (action.getLabel() == null || action.getIntent() == null) {
+                    Log.w(TAG, "Null action intent received. Skipping addition to notification");
+                    continue;
+                }
+                PendingIntent pendingActionIntent = action.toPendingIntent(mContext);
+                if (pendingActionIntent == null) {
+                    Log.w(TAG, "Null pending intent received. Skipping addition to notification");
+                    continue;
+                }
+                builder.addAction(0, action.getLabel(), pendingActionIntent);
 
-                        // Register receiver to learn if notification has been dismissed.
-                        // This is required to unregister receivers to prevent leak.
-                        Intent dismissIntent = new Intent(DISMISS_CSD_NOTIFICATION)
-                                .setPackage(mContext.getPackageName());
-                        PendingIntent pendingDismissIntent = PendingIntent.getBroadcast(mContext,
-                                0, dismissIntent, FLAG_IMMUTABLE);
-                        mBroadcastDispatcher.registerReceiver(mReceiverDismissNotification,
-                                new IntentFilter(DISMISS_CSD_NOTIFICATION),
-                                /* executor = default */ null,
-                                /* user = default */ null,
-                                Context.RECEIVER_NOT_EXPORTED,
-                                /* permission = default */ null);
-                        builder.setDeleteIntent(pendingDismissIntent);
-                    }
+                // Register receiver to undo volume only when
+                // notification conaining the undo action would be sent.
+                if (action.getLabel().equals(mContext.getString(R.string.volume_undo_action))) {
+                    final IntentFilter filterUndo = new IntentFilter(
+                            VolumeDialog.ACTION_VOLUME_UNDO);
+                    mBroadcastDispatcher.registerReceiver(mReceiverUndo,
+                            filterUndo,
+                            /* executor = default */ null,
+                            /* user = default */ null,
+                            Context.RECEIVER_NOT_EXPORTED,
+                            /* permission = default */ null);
+
+                    // Register receiver to learn if notification has been dismissed.
+                    // This is required to unregister receivers to prevent leak.
+                    Intent dismissIntent = new Intent(DISMISS_CSD_NOTIFICATION)
+                            .setPackage(mContext.getPackageName());
+                    PendingIntent pendingDismissIntent = PendingIntent.getBroadcast(
+                            mContext,
+                            0, dismissIntent, FLAG_IMMUTABLE);
+                    mBroadcastDispatcher.registerReceiver(mReceiverDismissNotification,
+                            new IntentFilter(DISMISS_CSD_NOTIFICATION),
+                            /* executor = default */ null,
+                            /* user = default */ null,
+                            Context.RECEIVER_NOT_EXPORTED,
+                            /* permission = default */ null);
+                    builder.setDeleteIntent(pendingDismissIntent);
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 0770d89..e56f6b3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -51,7 +51,6 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
-import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
@@ -79,7 +78,6 @@
 import android.provider.Settings.Global;
 import android.text.InputFilter;
 import android.util.Log;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
 import android.view.ContextThemeWrapper;
@@ -322,8 +320,8 @@
     private final VolumePanelFlag mVolumePanelFlag;
     private final VolumeDialogInteractor mInteractor;
     // Optional actions for soundDose
-    private Optional<ImmutableList<Pair<String, Intent>>> mCsdWarningNotificationActions =
-            Optional.of(ImmutableList.of());
+    private Optional<ImmutableList<CsdWarningAction>>
+            mCsdWarningNotificationActions = Optional.of(ImmutableList.of());
 
     public VolumeDialogImpl(
             Context context,
@@ -2231,7 +2229,7 @@
     }
 
     public void setCsdWarningNotificationActionIntents(
-            ImmutableList<Pair<String, Intent>> actionIntent) {
+            ImmutableList<CsdWarningAction> actionIntent) {
         mCsdWarningNotificationActions = Optional.of(actionIntent);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index dc2b80c..68d12f6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.volume;
 
+import static com.android.settingslib.flags.Flags.volumeDialogAudioSharingFix;
+
 import android.content.Context;
 import android.content.res.Configuration;
 import android.os.Handler;
@@ -26,6 +28,7 @@
 import com.android.systemui.qs.tiles.DndTile;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractor;
 
 import java.io.PrintWriter;
 
@@ -41,23 +44,29 @@
     private boolean mEnabled;
     private final Context mContext;
     private VolumeDialogComponent mVolumeComponent;
+    private AudioSharingInteractor mAudioSharingInteractor;
 
     @Inject
-    public VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent) {
+    public VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent,
+            AudioSharingInteractor audioSharingInteractor) {
         mContext = context;
         mVolumeComponent = volumeDialogComponent;
+        mAudioSharingInteractor = audioSharingInteractor;
     }
 
     @Override
     public void start() {
         boolean enableVolumeUi = mContext.getResources().getBoolean(R.bool.enable_volume_ui);
         boolean enableSafetyWarning =
-            mContext.getResources().getBoolean(R.bool.enable_safety_warning);
+                mContext.getResources().getBoolean(R.bool.enable_safety_warning);
         mEnabled = enableVolumeUi || enableSafetyWarning;
         if (!mEnabled) return;
 
         mVolumeComponent.setEnableDialogs(enableVolumeUi, enableSafetyWarning);
         setDefaultVolumeController();
+        if (volumeDialogAudioSharingFix()) {
+            mAudioSharingInteractor.handlePrimaryGroupChange();
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index 0c1bc21..efaca7a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -75,7 +75,6 @@
         @Provides
         @SysUISingleton
         fun provideAudioSharingRepository(
-            @Application context: Context,
             contentResolver: ContentResolver,
             localBluetoothManager: LocalBluetoothManager?,
             @Application coroutineScope: CoroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
index aba3015..2170c36 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
@@ -17,18 +17,28 @@
 package com.android.systemui.volume.domain.interactor
 
 import android.bluetooth.BluetoothCsipSetCoordinator
+import android.media.AudioManager.STREAM_MUSIC
 import androidx.annotation.IntRange
 import com.android.settingslib.volume.data.repository.AudioSharingRepository
 import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MAX
 import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MIN
+import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
+import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 interface AudioSharingInteractor {
     /** Audio sharing secondary headset volume changes. */
@@ -45,6 +55,16 @@
         @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
         level: Int
     )
+
+    /**
+     * Handle primary group change in audio sharing.
+     *
+     * Once the primary group is changed, we need to sync its volume to STREAM_MUSIC to make sure
+     * the volume adjustment during audio sharing can be kept after the sharing ends.
+     *
+     * TODO(b/355396988) Migrate to audio framework solution once it is in place.
+     */
+    fun handlePrimaryGroupChange()
 }
 
 @SysUISingleton
@@ -52,26 +72,60 @@
 @Inject
 constructor(
     @Application private val coroutineScope: CoroutineScope,
+    @Background private val backgroundCoroutineContext: CoroutineContext,
+    private val audioVolumeInteractor: AudioVolumeInteractor,
     private val audioSharingRepository: AudioSharingRepository
 ) : AudioSharingInteractor {
 
     override val volume: Flow<Int?> =
         combine(audioSharingRepository.secondaryGroupId, audioSharingRepository.volumeMap) {
-            secondaryGroupId,
-            volumeMap ->
-            if (secondaryGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) null
-            else volumeMap.getOrDefault(secondaryGroupId, DEFAULT_VOLUME)
-        }
+                secondaryGroupId,
+                volumeMap ->
+                if (secondaryGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) null
+                else volumeMap.getOrDefault(secondaryGroupId, DEFAULT_VOLUME)
+            }
+            .distinctUntilChanged()
 
     override val volumeMin: Int = AUDIO_SHARING_VOLUME_MIN
 
     override val volumeMax: Int = AUDIO_SHARING_VOLUME_MAX
 
-    override fun setStreamVolume(level: Int) {
+    override fun setStreamVolume(
+        @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
+        level: Int
+    ) {
         coroutineScope.launch { audioSharingRepository.setSecondaryVolume(level) }
     }
 
+    override fun handlePrimaryGroupChange() {
+        coroutineScope.launch {
+            audioSharingRepository.primaryGroupId
+                .map { primaryGroupId -> audioSharingRepository.volumeMap.value[primaryGroupId] }
+                .filterNotNull()
+                .distinctUntilChanged()
+                .collect {
+                    // Once primary device change, we need to update the STREAM_MUSIC volume to get
+                    // align with the primary device's volume
+                    setMusicStreamVolume(it)
+                }
+        }
+    }
+
+    private suspend fun setMusicStreamVolume(volume: Int) {
+        withContext(backgroundCoroutineContext) {
+            val musicStream =
+                audioVolumeInteractor.getAudioStream(AudioStream(STREAM_MUSIC)).first()
+            val musicVolume =
+                Math.round(
+                    volume.toFloat() * (musicStream.maxVolume - musicStream.minVolume) /
+                        (AUDIO_SHARING_VOLUME_MAX - AUDIO_SHARING_VOLUME_MIN)
+                )
+            audioVolumeInteractor.setVolume(AudioStream(STREAM_MUSIC), musicVolume)
+        }
+    }
+
     private companion object {
+        const val TAG = "AudioSharingInteractor"
         const val DEFAULT_VOLUME = 20
     }
 }
@@ -82,7 +136,12 @@
     override val volumeMin: Int = EMPTY_VOLUME
     override val volumeMax: Int = EMPTY_VOLUME
 
-    override fun setStreamVolume(level: Int) {}
+    override fun setStreamVolume(
+        @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
+        level: Int
+    ) {}
+
+    override fun handlePrimaryGroupChange() {}
 
     private companion object {
         const val EMPTY_VOLUME = 0
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index e68a4a5..9de7528 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -70,10 +70,10 @@
 import android.view.RemoteAnimationTarget;
 import android.view.View;
 import android.view.ViewRootImpl;
-import android.view.WindowManager;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.foldables.FoldGracePeriodProvider;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.UiEventLogger;
@@ -167,7 +167,7 @@
     private @Mock BroadcastDispatcher mBroadcastDispatcher;
     private @Mock DismissCallbackRegistry mDismissCallbackRegistry;
     private @Mock DumpManager mDumpManager;
-    private @Mock WindowManager mWindowManager;
+    private @Mock ViewCaptureAwareWindowManager mWindowManager;
     private @Mock IActivityManager mActivityManager;
     private @Mock ConfigurationController mConfigurationController;
     private @Mock PowerManager mPowerManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
index 196bbb9..413aa55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
@@ -307,7 +307,7 @@
 
         mNavigationBarController.mIsLargeScreen = false;
         mNavigationBarController.mIsPhone = true;
-        assertFalse(mNavigationBarController.supportsTaskbar());
+        assertTrue(mNavigationBarController.supportsTaskbar());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index 988769f..90ffaf1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -25,6 +25,7 @@
 import android.view.ContextThemeWrapper
 import android.view.View
 import android.view.accessibility.AccessibilityNodeInfo
+import android.widget.Button
 import android.widget.TextView
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -381,6 +382,47 @@
     }
 
     @Test
+    fun testNonSwitchA11yClass_longClickActionHasCorrectLabel() {
+        val state =
+            QSTile.State().apply {
+                expandedAccessibilityClassName = Button::class.java.name
+                handlesLongClick = true
+            }
+        tileView.changeState(state)
+        val info = AccessibilityNodeInfo(tileView)
+        tileView.onInitializeAccessibilityNodeInfo(info)
+
+        assertThat(
+                info.actionList
+                    .find {
+                        it.id == AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id
+                    }
+                    ?.label
+            )
+            .isEqualTo(context.getString(R.string.accessibility_long_click_tile))
+    }
+
+    @Test
+    fun testNonSwitchA11yClass_disabledByPolicy_noLongClickAction() {
+        val state =
+            QSTile.State().apply {
+                expandedAccessibilityClassName = Button::class.java.name
+                handlesLongClick = true
+                disabledByPolicy = true
+            }
+        tileView.changeState(state)
+        val info = AccessibilityNodeInfo(tileView)
+        tileView.onInitializeAccessibilityNodeInfo(info)
+
+        assertThat(
+                info.actionList.find {
+                    it.id == AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id
+                }
+            )
+            .isNull()
+    }
+
+    @Test
     fun onStateChange_longPressEffectActive_withInvalidDuration_doesNotInitializeEffect() {
         val state = QSTile.State() // A state that handles longPress
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 06a883c..b7ce336 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -44,7 +44,6 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -190,7 +189,6 @@
                 mKosmos.getKeyguardTransitionInteractor();
         KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
                 mKeyguardRepository,
-                new FakeCommandQueue(),
                 powerInteractor,
                 new FakeKeyguardBouncerRepository(),
                 new ConfigurationInteractor(configurationRepository),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
index e46906f..4762527 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
@@ -66,7 +66,8 @@
             repository = powerRepository,
             falsingCollector = mock(),
             screenOffAnimationController = mock(),
-            statusBarStateController = mock()
+            statusBarStateController = mock(),
+            cameraGestureHelper = mock(),
         )
 
     private val configurationRepository =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 5e5586d..d9e9495 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -42,6 +42,7 @@
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.emergency.EmergencyGestureModule.EmergencyGestureIntentFactory;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.recents.ScreenPinningRequest;
@@ -103,6 +104,7 @@
     @Mock private QSHost mQSHost;
     @Mock private ActivityStarter mActivityStarter;
     @Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
+    @Mock private KeyguardInteractor mKeyguardInteractor;
 
     CentralSurfacesCommandQueueCallbacks mSbcqCallbacks;
 
@@ -140,6 +142,7 @@
                 mUserTracker,
                 mQSHost,
                 mActivityStarter,
+                mKeyguardInteractor,
                 mEmergencyGestureIntentFactory);
 
         when(mUserTracker.getUserHandle()).thenReturn(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
index 49aedcc..bebf1cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
@@ -36,7 +36,6 @@
 import android.media.AudioManager;
 import android.platform.test.annotations.EnableFlags;
 import android.testing.TestableLooper;
-import android.util.Pair;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -70,6 +69,8 @@
     private CsdWarningDialog mDialog;
     private static final String DISMISS_CSD_NOTIFICATION =
             "com.android.systemui.volume.DISMISS_CSD_NOTIFICATION";
+    private final Optional<ImmutableList<CsdWarningAction>> mEmptyActions =
+            Optional.of(ImmutableList.of());
 
     @Before
     public void setup() {
@@ -87,7 +88,7 @@
         // instantiate directly instead of via factory; we don't want executor to be @Background
         mDialog = new CsdWarningDialog(CSD_WARNING_DOSE_REACHED_1X, mContext,
                 mAudioManager, mNotificationManager, executor, null,
-                Optional.of(ImmutableList.of(new Pair("", new Intent()))),
+                mEmptyActions,
                 mFakeBroadcastDispatcher);
 
         mDialog.show();
@@ -104,7 +105,7 @@
         FakeExecutor executor =  new FakeExecutor(new FakeSystemClock());
         mDialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext,
                 mAudioManager, mNotificationManager, executor, null,
-                Optional.of(ImmutableList.of(new Pair("", new Intent()))),
+                mEmptyActions,
                 mFakeBroadcastDispatcher);
 
         mDialog.show();
@@ -121,7 +122,7 @@
                 .setPackage(mContext.getPackageName());
         mDialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext,
                 mAudioManager, mNotificationManager, executor, null,
-                Optional.of(ImmutableList.of(new Pair("Undo", undoIntent))),
+                Optional.of(ImmutableList.of(new CsdWarningAction("Undo", undoIntent, false))),
                 mFakeBroadcastDispatcher);
 
         when(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)).thenReturn(25);
@@ -148,7 +149,7 @@
                 .setPackage(mContext.getPackageName());
         mDialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext,
                 mAudioManager, mNotificationManager, executor, null,
-                Optional.of(ImmutableList.of(new Pair("Undo", undoIntent))),
+                Optional.of(ImmutableList.of(new CsdWarningAction("Undo", undoIntent, false))),
                 mFakeBroadcastDispatcher);
         Intent dismissIntent = new Intent(DISMISS_CSD_NOTIFICATION)
                 .setPackage(mContext.getPackageName());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 3f5dc82..8b7d921 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -252,7 +252,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
     public void handleAudioSharingStreamVolumeChanges_updateState() {
         ArgumentCaptor<VolumeDialogController.State> stateCaptor =
                 ArgumentCaptor.forClass(VolumeDialogController.State.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index b5cbf59..caa1779 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -44,7 +44,6 @@
 import static org.mockito.Mockito.when;
 
 import android.app.KeyguardManager;
-import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -57,7 +56,6 @@
 import android.provider.Settings;
 import android.testing.TestableLooper;
 import android.util.Log;
-import android.util.Pair;
 import android.view.Gravity;
 import android.view.InputDevice;
 import android.view.MotionEvent;
@@ -166,7 +164,7 @@
             new CsdWarningDialog.Factory() {
                 @Override
                 public CsdWarningDialog create(int warningType, Runnable onCleanup,
-                        Optional<ImmutableList<Pair<String, Intent>>> actionIntents) {
+                        Optional<ImmutableList<CsdWarningAction>> actionIntents) {
                     return mCsdWarningDialog;
                 }
             };
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 60b5b5d..d6b4d2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -98,6 +98,8 @@
 import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
+import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
@@ -210,6 +212,7 @@
 import java.util.Optional;
 import java.util.concurrent.Executor;
 
+import kotlin.Lazy;
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
 import platform.test.runner.parameterized.Parameters;
 
@@ -342,6 +345,8 @@
     private Icon mAppBubbleIcon;
     @Mock
     private Display mDefaultDisplay;
+    @Mock
+    private Lazy<ViewCapture> mLazyViewCapture;
 
     private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
     private ShadeInteractor mShadeInteractor;
@@ -407,7 +412,8 @@
         mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
                 mContext,
                 new FakeWindowRootViewComponent.Factory(mNotificationShadeWindowView),
-                mWindowManager,
+                new ViewCaptureAwareWindowManager(mWindowManager, mLazyViewCapture,
+                        /* isViewCaptureEnabled= */ false),
                 mActivityManager,
                 mDozeParameters,
                 mStatusBarStateController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index 0b6b816..5063140 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.camera.CameraGestureHelper
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
@@ -143,6 +144,7 @@
     @get:Provides val primaryBouncerInteractor: PrimaryBouncerInteractor = mock(),
     @get:Provides val keyguardStateController: KeyguardStateController = mock(),
     @get:Provides val globalSettings: GlobalSettings = mock(),
+    @get:Provides val cameraGestureHelper: CameraGestureHelper = mock(),
 
     // log buffers
     @get:[Provides BroadcastDispatcherLog]
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/camera/CameraGestureHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/CameraGestureHelperKosmos.kt
new file mode 100644
index 0000000..931567e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/CameraGestureHelperKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.camera
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import org.mockito.Mockito.mock
+
+val Kosmos.cameraGestureHelper: CameraGestureHelper by
+    Kosmos.Fixture<CameraGestureHelper> {
+        mock(CameraGestureHelper::class.java).also { helper ->
+            whenever(helper.canCameraGestureBeLaunched(any())).thenReturn(true)
+        }
+    }
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 87143ef..727de9e 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
@@ -22,6 +22,7 @@
 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
 import com.android.systemui.keyguard.shared.model.DismissAction
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.KeyguardDone
@@ -138,6 +139,8 @@
     private val _canIgnoreAuthAndReturnToGone = MutableStateFlow(false)
     override val canIgnoreAuthAndReturnToGone = _canIgnoreAuthAndReturnToGone.asStateFlow()
 
+    override val onCameraLaunchDetected = MutableStateFlow(CameraLaunchSourceModel())
+
     override fun setQuickSettingsVisible(isVisible: Boolean) {
         _isQuickSettingsVisible.value = isVisible
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index b5ca964..a95609e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -49,7 +48,6 @@
     fun create(
         featureFlags: FakeFeatureFlags = FakeFeatureFlags(),
         repository: FakeKeyguardRepository = FakeKeyguardRepository(),
-        commandQueue: FakeCommandQueue = FakeCommandQueue(),
         bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
         configurationRepository: FakeConfigurationRepository = FakeConfigurationRepository(),
         shadeRepository: FakeShadeRepository = FakeShadeRepository(),
@@ -87,7 +85,6 @@
                 }
         return WithDependencies(
             repository = repository,
-            commandQueue = commandQueue,
             featureFlags = featureFlags,
             bouncerRepository = bouncerRepository,
             configurationRepository = configurationRepository,
@@ -95,7 +92,6 @@
             powerInteractor = powerInteractor,
             KeyguardInteractor(
                 repository = repository,
-                commandQueue = commandQueue,
                 powerInteractor = powerInteractor,
                 bouncerRepository = bouncerRepository,
                 configurationInteractor = ConfigurationInteractor(configurationRepository),
@@ -112,7 +108,6 @@
 
     data class WithDependencies(
         val repository: FakeKeyguardRepository,
-        val commandQueue: FakeCommandQueue,
         val featureFlags: FakeFeatureFlags,
         val bouncerRepository: FakeKeyguardBouncerRepository,
         val configurationRepository: FakeConfigurationRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
index 81d8f0b..5ab56e9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
@@ -18,7 +18,6 @@
 
 import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
 import com.android.systemui.common.ui.domain.interactor.configurationInteractor
-import com.android.systemui.keyguard.data.repository.fakeCommandQueue
 import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
@@ -31,7 +30,6 @@
     Kosmos.Fixture {
         KeyguardInteractor(
             repository = keyguardRepository,
-            commandQueue = fakeCommandQueue,
             powerInteractor = powerInteractor,
             bouncerRepository = keyguardBouncerRepository,
             configurationInteractor = configurationInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorFactory.kt
index d92ace9..9a07c4e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorFactory.kt
@@ -42,6 +42,7 @@
                     falsingCollector,
                     screenOffAnimationController,
                     statusBarStateController,
+                    mock(),
                 )
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorKosmos.kt
index 8486691..d50091e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.power.domain.interactor
 
+import com.android.systemui.camera.cameraGestureHelper
 import com.android.systemui.classifier.falsingCollector
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.plugins.statusbar.statusBarStateController
@@ -29,5 +30,6 @@
             falsingCollector = falsingCollector,
             screenOffAnimationController = screenOffAnimationController,
             statusBarStateController = statusBarStateController,
+            cameraGestureHelper = { cameraGestureHelper },
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
index 476b7d8..6417779 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
@@ -38,6 +38,11 @@
 
     public static final Uri CONTENT_URI = Uri.parse("content://settings/fake_global");
 
+    /**
+     * @deprecated Please use FakeGlobalSettings(testDispatcher) to provide the same dispatcher used
+     * by main test scope.
+     */
+    @Deprecated
     public FakeGlobalSettings() {
         mDispatcher = StandardTestDispatcher(/* scheduler = */ null, /* name = */ null);
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt
index df6fc41..35fa2af 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt
@@ -18,5 +18,6 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
 
-val Kosmos.fakeGlobalSettings: FakeGlobalSettings by Fixture { FakeGlobalSettings() }
+val Kosmos.fakeGlobalSettings: FakeGlobalSettings by Fixture { FakeGlobalSettings(testDispatcher) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
index e35da11..e4e2481 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
@@ -47,6 +47,11 @@
     @UserIdInt
     private int mUserId = UserHandle.USER_CURRENT;
 
+    /**
+     * @deprecated Please use FakeSettings(testDispatcher) to provide the same dispatcher used
+     * by main test scope.
+     */
+    @Deprecated
     public FakeSettings() {
         mDispatcher = StandardTestDispatcher(/* scheduler = */ null, /* name = */ null);
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt
index bcb5848..55044bf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt
@@ -18,5 +18,7 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.settings.fakeUserTracker
 
-val Kosmos.fakeSettings: FakeSettings by Fixture { FakeSettings() }
+val Kosmos.fakeSettings: FakeSettings by Fixture { FakeSettings(testDispatcher, fakeUserTracker) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
index d391750..0a617d1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
@@ -16,10 +16,7 @@
 
 package com.android.systemui.volume.data.repository
 
-import androidx.annotation.IntRange
 import com.android.settingslib.volume.data.repository.AudioSharingRepository
-import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MAX
-import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MIN
 import com.android.settingslib.volume.data.repository.GroupIdToVolumes
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -27,11 +24,14 @@
 
 class FakeAudioSharingRepository : AudioSharingRepository {
     private val mutableInAudioSharing: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    private val mutablePrimaryGroupId: MutableStateFlow<Int> =
+        MutableStateFlow(TEST_GROUP_ID_INVALID)
     private val mutableSecondaryGroupId: MutableStateFlow<Int> =
         MutableStateFlow(TEST_GROUP_ID_INVALID)
     private val mutableVolumeMap: MutableStateFlow<GroupIdToVolumes> = MutableStateFlow(emptyMap())
 
     override val inAudioSharing: Flow<Boolean> = mutableInAudioSharing
+    override val primaryGroupId: StateFlow<Int> = mutablePrimaryGroupId
     override val secondaryGroupId: StateFlow<Int> = mutableSecondaryGroupId
     override val volumeMap: StateFlow<GroupIdToVolumes> = mutableVolumeMap
 
@@ -41,6 +41,10 @@
         mutableInAudioSharing.value = state
     }
 
+    fun setPrimaryGroupId(groupId: Int) {
+        mutablePrimaryGroupId.value = groupId
+    }
+
     fun setSecondaryGroupId(groupId: Int) {
         mutableSecondaryGroupId.value = groupId
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorKosmos.kt
index 03981bb..ce8aba5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorKosmos.kt
@@ -18,12 +18,15 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.backgroundCoroutineContext
 import com.android.systemui.volume.data.repository.audioSharingRepository
 
 val Kosmos.audioSharingInteractor by
     Kosmos.Fixture {
         AudioSharingInteractorImpl(
             applicationCoroutineScope,
+            backgroundCoroutineContext,
+            audioVolumeInteractor,
             audioSharingRepository,
         )
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java
index 8a2bc1d..f7a59a4b 100644
--- a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java
@@ -26,7 +26,6 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.util.Slog;
-import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -60,6 +59,7 @@
     private final Set<AccessibilityHierarchyCheck> mHierarchyChecks;
     private final ATFHierarchyBuilder mATFHierarchyBuilder;
     private final Set<AccessibilityCheckResultReported> mCachedResults = new HashSet<>();
+
     @VisibleForTesting
     final A11yCheckerTimer mTimer = new A11yCheckerTimer();
 
@@ -86,10 +86,8 @@
      */
     @RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
     public Set<AccessibilityCheckResultReported> maybeRunA11yChecker(
-            List<AccessibilityNodeInfo> nodes,
-            @Nullable AccessibilityEvent accessibilityEvent,
-            ComponentName sourceComponentName,
-            @UserIdInt int userId) {
+            List<AccessibilityNodeInfo> nodes, @Nullable String sourceEventClassName,
+            ComponentName a11yServiceComponentName, @UserIdInt int userId) {
         if (!shouldRunA11yChecker()) {
             return Set.of();
         }
@@ -108,14 +106,13 @@
                 List<AccessibilityHierarchyCheckResult> checkResults = runChecksOnNode(nodeInfo);
                 Set<AccessibilityCheckResultReported> filteredResults =
                         AccessibilityCheckerUtils.processResults(nodeInfo, checkResults,
-                                accessibilityEvent, mPackageManager, sourceComponentName);
+                                sourceEventClassName, mPackageManager, a11yServiceComponentName);
                 allResults.addAll(filteredResults);
             }
             mCachedResults.addAll(allResults);
         } catch (RuntimeException e) {
             Slog.e(LOG_TAG, "An unknown error occurred while running a11y checker.", e);
         }
-
         return allResults;
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
index 4171108..fa0fed2 100644
--- a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
@@ -22,7 +22,6 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.util.Slog;
-import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -96,7 +95,7 @@
     static Set<AccessibilityCheckResultReported> processResults(
             AccessibilityNodeInfo nodeInfo,
             List<AccessibilityHierarchyCheckResult> checkResults,
-            @Nullable AccessibilityEvent accessibilityEvent,
+            @Nullable String activityClassName,
             PackageManager packageManager,
             ComponentName a11yServiceComponentName) {
         String appPackageName = nodeInfo.getPackageName().toString();
@@ -110,7 +109,8 @@
                     .setPackageName(appPackageName)
                     .setAppVersionCode(getAppVersionCode(packageManager, appPackageName))
                     .setUiElementPath(nodePath)
-                    .setActivityName(getActivityName(packageManager, accessibilityEvent))
+                    .setActivityName(
+                            getActivityName(packageManager, appPackageName, activityClassName))
                     .setWindowTitle(getWindowTitle(nodeInfo))
                     .setSourceComponentName(a11yServiceComponentName.flattenToString())
                     .setSourceVersionCode(
@@ -140,31 +140,23 @@
     }
 
     /**
-     * Returns the simple class name of the Activity providing the cache update, if available,
+     * Returns the simple class name of the Activity associated with the window, if available,
      * or an empty String if not.
      */
     @VisibleForTesting
     static String getActivityName(
-            PackageManager packageManager, @Nullable AccessibilityEvent accessibilityEvent) {
-        if (accessibilityEvent == null) {
+            PackageManager packageManager, String packageName, @Nullable String activityClassName) {
+        if (activityClassName == null) {
             return "";
         }
-        CharSequence activityName = accessibilityEvent.getClassName();
-        if (accessibilityEvent.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
-                && accessibilityEvent.getPackageName() != null
-                && activityName != null) {
-            try {
-                // Check class is for a valid Activity.
-                packageManager
-                        .getActivityInfo(
-                                new ComponentName(accessibilityEvent.getPackageName().toString(),
-                                        activityName.toString()), 0);
-                int qualifierEnd = activityName.toString().lastIndexOf('.');
-                return activityName.toString().substring(qualifierEnd + 1);
-            } catch (PackageManager.NameNotFoundException e) {
-                // No need to spam the logs. This is very frequent when the class doesn't match
-                // an activity.
-            }
+        try {
+            // Check class is for a valid Activity.
+            packageManager.getActivityInfo(new ComponentName(packageName, activityClassName), 0);
+            int qualifierEnd = activityClassName.lastIndexOf('.');
+            return activityClassName.substring(qualifierEnd + 1);
+        } catch (PackageManager.NameNotFoundException e) {
+            // No need to spam the logs. This is very frequent when the class doesn't match
+            // an activity.
         }
         return "";
     }
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 4ff1367..afb7bb4 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -538,6 +538,8 @@
                             if (!pr.hasProvider(cpi.name)) {
                                 checkTime(startTime, "getContentProviderImpl: scheduling install");
                                 pr.installProvider(cpi.name, cpr);
+                                mService.mOomAdjuster.unfreezeTemporarily(proc,
+                                        CachedAppOptimizer.UNFREEZE_REASON_GET_PROVIDER);
                                 try {
                                     thread.scheduleInstallProvider(cpi);
                                 } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index f813997..55de9aa 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1260,8 +1260,8 @@
      * @return true if drag and drop was successfully started, false otherwise.
      */
     public boolean startDragAndDrop(@NonNull IBinder fromChannelToken,
-            @NonNull InputChannel dragAndDropChannel) {
-        return mNative.transferTouchGesture(fromChannelToken, dragAndDropChannel.getToken(),
+            @NonNull IBinder dragAndDropChannelToken) {
+        return mNative.transferTouchGesture(fromChannelToken, dragAndDropChannelToken,
                 true /* isDragDrop */);
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index 99f4747..b08f917 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -38,10 +38,11 @@
 final class AdditionalSubtypeMapRepository {
     private static final String TAG = "AdditionalSubtypeMapRepository";
 
-    // TODO(b/352594784): Should we user other lock primitives?
-    @GuardedBy("sPerUserMap")
+    private static final Object sMutationLock = new Object();
+
     @NonNull
-    private static final SparseArray<AdditionalSubtypeMap> sPerUserMap = new SparseArray<>();
+    private static volatile ImmutableSparseArray<AdditionalSubtypeMap> sPerUserMap =
+            ImmutableSparseArray.empty();
 
     record WriteTask(@UserIdInt int userId, @NonNull AdditionalSubtypeMap subtypeMap,
                      @NonNull InputMethodMap inputMethodMap) {
@@ -198,7 +199,7 @@
     /**
      * Returns {@link AdditionalSubtypeMap} for the given user.
      *
-     * <p>This method is expected be called after {@link #ensureInitializedAndGet(int)}. Otherwise
+     * <p>This method is expected be called after {@link #initializeIfNecessary(int)}. Otherwise
      * {@link AdditionalSubtypeMap#EMPTY_MAP} will be returned.</p>
      *
      * @param userId the user to be queried about
@@ -207,10 +208,7 @@
     @AnyThread
     @NonNull
     static AdditionalSubtypeMap get(@UserIdInt int userId) {
-        final AdditionalSubtypeMap map;
-        synchronized (sPerUserMap) {
-            map = sPerUserMap.get(userId);
-        }
+        final AdditionalSubtypeMap map = sPerUserMap.get(userId);
         if (map == null) {
             Slog.e(TAG, "get(userId=" + userId + ") is called before loadInitialDataAndGet()."
                     + " Returning an empty map");
@@ -220,28 +218,24 @@
     }
 
     /**
-     * Ensures that {@link AdditionalSubtypeMap} is initialized for the given user. Load it from
-     * the persistent storage if {@link #putAndSave(int, AdditionalSubtypeMap, InputMethodMap)} has
-     * not been called yet.
+     * Ensures that {@link AdditionalSubtypeMap} is initialized for the given user.
      *
      * @param userId the user to be initialized
-     * @return {@link AdditionalSubtypeMap} that is associated with the given user. If
-     *         {@link #putAndSave(int, AdditionalSubtypeMap, InputMethodMap)} is already called
-     *         then the given {@link AdditionalSubtypeMap}.
      */
     @AnyThread
     @NonNull
-    static AdditionalSubtypeMap ensureInitializedAndGet(@UserIdInt int userId) {
-        final var map = AdditionalSubtypeUtils.load(userId);
-        synchronized (sPerUserMap) {
-            final AdditionalSubtypeMap previous = sPerUserMap.get(userId);
-            // If putAndSave() has already been called, then use it.
-            if (previous != null) {
-                return previous;
-            }
-            sPerUserMap.put(userId, map);
+    static void initializeIfNecessary(@UserIdInt int userId) {
+        if (sPerUserMap.contains(userId)) {
+            // Fast-pass. If putAndSave() is already called, then do nothing.
+            return;
         }
-        return map;
+        final var map = AdditionalSubtypeUtils.load(userId);
+        synchronized (sMutationLock) {
+            // Check the condition again.
+            if (!sPerUserMap.contains(userId)) {
+                sPerUserMap = sPerUserMap.cloneWithPutOrSelf(userId, map);
+            }
+        }
     }
 
     /**
@@ -255,12 +249,8 @@
     @AnyThread
     static void putAndSave(@UserIdInt int userId, @NonNull AdditionalSubtypeMap map,
             @NonNull InputMethodMap inputMethodMap) {
-        synchronized (sPerUserMap) {
-            final AdditionalSubtypeMap previous = sPerUserMap.get(userId);
-            if (previous == map) {
-                return;
-            }
-            sPerUserMap.put(userId, map);
+        synchronized (sMutationLock) {
+            sPerUserMap = sPerUserMap.cloneWithPutOrSelf(userId, map);
             sWriter.scheduleWriteTask(userId, map, inputMethodMap);
         }
     }
@@ -277,9 +267,9 @@
 
     @AnyThread
     static void remove(@UserIdInt int userId) {
-        synchronized (sPerUserMap) {
+        synchronized (sMutationLock) {
             sWriter.onUserRemoved(userId);
-            sPerUserMap.remove(userId);
+            sPerUserMap = sPerUserMap.cloneWithRemoveOrSelf(userId);
         }
     }
 }
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index ae41133..cdea6ff 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -39,6 +39,7 @@
 import android.annotation.AnyThread;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.res.Configuration;
 import android.os.Binder;
@@ -128,6 +129,15 @@
     @GuardedBy("ImfLock.class")
     private IBinder mCurVisibleImeInputTarget;
 
+    /**
+     * The last window token that we confirmed that IME started talking to.  This is always updated
+     * upon reports from the input method.  If the window state is already changed before the report
+     * is handled, this field just keeps the last value.
+     */
+    @GuardedBy("ImfLock.class")
+    @Nullable
+    private IBinder mLastImeTargetWindow;
+
     /** Represent the invalid IME visibility state */
     public static final int STATE_INVALID = -1;
 
@@ -479,8 +489,7 @@
                 break;
             case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
                 // Do nothing but preserving the last IME requested visibility state.
-                final ImeTargetWindowState lastState =
-                        getWindowStateOrNull(mService.mLastImeTargetWindow);
+                final ImeTargetWindowState lastState = getWindowStateOrNull(mLastImeTargetWindow);
                 if (lastState != null) {
                     state.setRequestedImeVisible(lastState.mRequestedImeVisible);
                 }
@@ -632,6 +641,17 @@
     }
 
     @GuardedBy("ImfLock.class")
+    @Nullable
+    IBinder getLastImeTargetWindow() {
+        return mLastImeTargetWindow;
+    }
+
+    @GuardedBy("ImfLock.class")
+    void setLastImeTargetWindow(@Nullable IBinder imeTargetWindow) {
+        mLastImeTargetWindow = imeTargetWindow;
+    }
+
+    @GuardedBy("ImfLock.class")
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
         proto.write(SHOW_EXPLICITLY_REQUESTED, mRequestedShowExplicitly);
         proto.write(SHOW_FORCED, mShowForced);
@@ -647,6 +667,7 @@
                 + " mShowForced=" + mShowForced);
         p.println(prefix + "mImeHiddenByDisplayPolicy=" + mPolicy.isImeHiddenByDisplayPolicy());
         p.println(prefix + "mInputShown=" + mInputShown);
+        p.println(prefix + "mLastImeTargetWindow=" + mLastImeTargetWindow);
     }
 
     /**
diff --git a/services/core/java/com/android/server/inputmethod/ImmutableSparseArray.java b/services/core/java/com/android/server/inputmethod/ImmutableSparseArray.java
new file mode 100644
index 0000000..382aa8a
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ImmutableSparseArray.java
@@ -0,0 +1,183 @@
+/*
+ * 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.server.inputmethod;
+
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.SparseArray;
+
+import java.util.function.Consumer;
+
+/**
+ * A holder object to expose {@link SparseArray} to multiple threads in a thread-safe manner through
+ * "final Field Semantics" defined in JLS 17.5, with only exposing thread-safe methods such as
+ * {@link SparseArray#get(int)} and {@link SparseArray#size()} from {@link SparseArray}, and with
+ * adding clone-with-update style methods {@link #cloneWithPutOrSelf(int, Object)} and
+ * {@link #cloneWithRemoveOrSelf(int)} instead of exposing mutation methods.
+ *
+ * @param <E> Type of the element
+ */
+final class ImmutableSparseArray<E> {
+    @NonNull
+    private final SparseArray<E> mArray;
+
+    private static final ImmutableSparseArray<Object> EMPTY =
+            new ImmutableSparseArray<>(new SparseArray<>());
+
+    /**
+     * Returns an empty {@link ImmutableSparseArray} instance.
+     *
+     * @return An empty {@link ImmutableSparseArray} instance.
+     * @param <T> Type of the element
+     */
+    @SuppressWarnings("unchecked")
+    @AnyThread
+    @NonNull
+    static <T> ImmutableSparseArray<T> empty() {
+        return (ImmutableSparseArray<T>) EMPTY;
+    }
+
+    private ImmutableSparseArray(@NonNull SparseArray<E> array) {
+        mArray = array;
+    }
+
+    /**
+     * @return the size of this array
+     */
+    @AnyThread
+    int size() {
+        return mArray.size();
+    }
+
+    /**
+     * Returns the key of the specified index.
+     *
+     * @return the key of the specified index
+     * @throws ArrayIndexOutOfBoundsException when the index is out of range
+     */
+    @AnyThread
+    int keyAt(int index) {
+        return mArray.keyAt(index);
+    }
+
+    /**
+     * Returns the value of the specified index.
+     *
+     * @return the value of the specified index
+     * @throws ArrayIndexOutOfBoundsException when the index is out of range
+     */
+    @AnyThread
+    @Nullable
+    public E valueAt(int index) {
+        return mArray.valueAt(index);
+    }
+
+    /**
+     * Returns the index of the specified key.
+     *
+     * @return the index of the specified key if exists. Otherwise {@code -1}
+     */
+    @AnyThread
+    int indexOfKey(int key) {
+        return mArray.indexOfKey(key);
+    }
+
+    /**
+     * Returns {@code true} if the given {@code key} exists.
+     *
+     * @param key the key to be queried
+     * @return    {@code true} if the given {@code key} exists
+     */
+    @AnyThread
+    boolean contains(int key) {
+        return mArray.contains(key);
+    }
+
+    /**
+     * Returns the value associated with the {@code key}.
+     *
+     * @param key the key to be queried
+     * @return    the value associated with the {@code key} if exists. Otherwise {@code null}
+     */
+    @AnyThread
+    @Nullable
+    E get(int key) {
+        return mArray.get(key);
+    }
+
+    /**
+     * Run {@link Consumer} for each value.
+     *
+     * @param consumer {@link Consumer} to be called back
+     */
+    @AnyThread
+    void forEach(@NonNull Consumer<E> consumer) {
+        final int size = mArray.size();
+        for (int i = 0; i < size; ++i) {
+            consumer.accept(mArray.valueAt(i));
+        }
+    }
+
+    /**
+     * Returns an instance of {@link ImmutableSparseArray} that has the given key and value on top
+     * of items cloned from this instance.
+     *
+     * @param key   the key to be added
+     * @param value the value to be added
+     * @return      the same {@link ImmutableSparseArray} instance if there is actually no update.
+     *              Otherwise, a new instance of {@link ImmutableSparseArray}
+     */
+    @AnyThread
+    @NonNull
+    ImmutableSparseArray<E> cloneWithPutOrSelf(int key, @Nullable E value) {
+        final var prevKeyIndex = mArray.indexOfKey(key);
+        if (prevKeyIndex >= 0) {
+            final var prevValue = mArray.valueAt(prevKeyIndex);
+            if (prevValue == value) {
+                return this;
+            }
+        }
+        final var clone = mArray.clone();
+        clone.put(key, value);
+        return new ImmutableSparseArray<>(clone);
+    }
+
+    /**
+     * Returns an instance of {@link ImmutableSparseArray} that does not have the given key on top
+     * of items cloned from this instance.
+     *
+     * @param key the key to be removed
+     * @return    the same {@link ImmutableSparseArray} instance if there is actually no update.
+     *            Otherwise, a new instance of {@link ImmutableSparseArray}
+     */
+    @AnyThread
+    @NonNull
+    ImmutableSparseArray<E> cloneWithRemoveOrSelf(int key) {
+        final int index = indexOfKey(key);
+        if (index < 0) {
+            return this;
+        }
+        if (mArray.size() == 1) {
+            return empty();
+        }
+        final var clone = mArray.clone();
+        clone.remove(key);
+        return new ImmutableSparseArray<>(clone);
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 9837ab1..03cbab5 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -463,7 +463,7 @@
                     // should now try to restart the service for us.
                     mLastBindTime = SystemClock.uptimeMillis();
                     clearCurMethodAndSessions();
-                    mService.clearInputShownLocked();
+                    mService.mVisibilityStateComputer.setInputShown(false);
                     mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME, mUserId);
                 }
             }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 9d007c6..effecd4 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -81,6 +81,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.hardware.input.InputManager;
 import android.inputmethodservice.InputMethodService;
@@ -258,7 +259,6 @@
     private static final int MSG_HIDE_ALL_INPUT_METHODS = 1035;
     private static final int MSG_REMOVE_IME_SURFACE = 1060;
     private static final int MSG_REMOVE_IME_SURFACE_FROM_WINDOW = 1061;
-    private static final int MSG_UPDATE_IME_WINDOW_STATUS = 1070;
 
     private static final int MSG_RESET_HANDWRITING = 1090;
     private static final int MSG_START_HANDWRITING = 1100;
@@ -305,6 +305,28 @@
     private final String[] mNonPreemptibleInputMethods;
 
     /**
+     * Whether the new Input Method Switcher menu is enabled.
+     *
+     * @see #shouldEnableNewInputMethodSwitcherMenu
+     */
+    @SharedByAllUsersField
+    private final boolean mNewInputMethodSwitcherMenuEnabled;
+
+    /**
+     * Returns {@code true} if the new Input Method Switcher menu is enabled. This will be
+     * {@code false} for watches and small screen devices.
+     *
+     * @param context the context to check the device configuration for.
+     */
+    private static boolean shouldEnableNewInputMethodSwitcherMenu(@NonNull Context context) {
+        final boolean isWatch = context.getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_WATCH);
+        final boolean isSmallScreen = (context.getResources().getConfiguration().screenLayout
+                & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_SMALL;
+        return Flags.imeSwitcherRevamp() && !isWatch && !isSmallScreen;
+    }
+
+    /**
      * See {@link #shouldEnableConcurrentMultiUserMode(Context)} about when set to be {@code true}.
      */
     @SharedByAllUsersField
@@ -339,6 +361,35 @@
         return mConcurrentMultiUserModeEnabled ? callingProcessUserId : mCurrentUserId;
     }
 
+    /**
+     * Figures out the target IME user ID associated with the given {@code displayId}.
+     *
+     * @param displayId the display ID to be queried about
+     * @return User ID to be used for this {@code displayId}.
+     */
+    @GuardedBy("ImfLock.class")
+    @UserIdInt
+    private int resolveImeUserIdFromDisplayIdLocked(int displayId) {
+        return mConcurrentMultiUserModeEnabled
+                ? mUserManagerInternal.getUserAssignedToDisplay(displayId) : mCurrentUserId;
+    }
+
+    /**
+     * Figures out the target IME user ID associated with the given {@code windowToken}.
+     *
+     * @param windowToken the Window token to be queried about
+     * @return User ID to be used for this {@code displayId}.
+     */
+    @GuardedBy("ImfLock.class")
+    @UserIdInt
+    private int resolveImeUserIdFromWindowLocked(@NonNull IBinder windowToken) {
+        if (mConcurrentMultiUserModeEnabled) {
+            final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken);
+            return mUserManagerInternal.getUserAssignedToDisplay(displayId);
+        }
+        return mCurrentUserId;
+    }
+
     final Context mContext;
     final Resources mRes;
     private final Handler mHandler;
@@ -372,7 +423,7 @@
     @GuardedBy("ImfLock.class")
     @MultiUserUnawareField
     @NonNull
-    private final ImeVisibilityStateComputer mVisibilityStateComputer;
+    final ImeVisibilityStateComputer mVisibilityStateComputer;
 
     @GuardedBy("ImfLock.class")
     @SharedByAllUsersField
@@ -495,14 +546,6 @@
     }
 
     /**
-     * The last window token that we confirmed that IME started talking to.  This is always updated
-     * upon reports from the input method.  If the window state is already changed before the report
-     * is handled, this field just keeps the last value.
-     */
-    @MultiUserUnawareField
-    IBinder mLastImeTargetWindow;
-
-    /**
      * Map of window perceptible states indexed by their associated window tokens.
      *
      * The value {@code true} indicates that IME has not been mostly hidden via
@@ -571,7 +614,7 @@
     private void onSecureSettingsChangedLocked(@NonNull String key, @UserIdInt int userId) {
         switch (key) {
             case Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD: {
-                if (!Flags.imeSwitcherRevamp()) {
+                if (!mNewInputMethodSwitcherMenuEnabled) {
                     if (userId == mCurrentUserId) {
                         mMenuController.updateKeyboardFromSettingsLocked();
                     }
@@ -640,7 +683,7 @@
                         }
                     }
                 }
-                if (Flags.imeSwitcherRevamp()) {
+                if (mNewInputMethodSwitcherMenuEnabled) {
                     synchronized (ImfLock.class) {
                         final var bindingController = getInputMethodBindingController(senderUserId);
                         mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(),
@@ -944,7 +987,7 @@
             // TODO(b/196206770): Disallow I/O on this thread. Currently it's needed for loading
             // additional subtypes in switchUserOnHandlerLocked().
             final ServiceThread thread = new ServiceThread(HANDLER_THREAD_NAME,
-                    Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
+                    Process.THREAD_PRIORITY_FOREGROUND, false /* allowIo */);
             thread.start();
 
             final ServiceThread ioThread = new ServiceThread(PACKAGE_MONITOR_THREAD_NAME,
@@ -1063,8 +1106,8 @@
 
                 for (int userId : userIds) {
                     Slog.d(TAG, "Start initialization for user=" + userId);
-                    final var additionalSubtypeMap =
-                            AdditionalSubtypeMapRepository.ensureInitializedAndGet(userId);
+                    AdditionalSubtypeMapRepository.initializeIfNecessary(userId);
+                    final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
                     final var settings = InputMethodManagerService.queryInputMethodServicesInternal(
                             context, userId, additionalSubtypeMap,
                             DirectBootAwareness.AUTO).getMethodMap();
@@ -1131,6 +1174,7 @@
             mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
 
             mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
+            mNewInputMethodSwitcherMenuEnabled = shouldEnableNewInputMethodSwitcherMenu(mContext);
 
             mShowOngoingImeSwitcherForPhones = false;
 
@@ -1143,7 +1187,7 @@
                             : bindingControllerFactory);
 
             mMenuController = new InputMethodMenuController(this);
-            mMenuControllerNew = Flags.imeSwitcherRevamp()
+            mMenuControllerNew = mNewInputMethodSwitcherMenuEnabled
                     ? new InputMethodMenuControllerNew() : null;
             mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
             mVisibilityApplier = new DefaultImeVisibilityApplier(this);
@@ -1764,7 +1808,7 @@
                     ImeTracker.PHASE_SERVER_WAIT_IME);
             userData.mCurStatsToken = null;
             // TODO: Make mMenuController multi-user aware
-            if (Flags.imeSwitcherRevamp()) {
+            if (mNewInputMethodSwitcherMenuEnabled) {
                 mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
             } else {
                 mMenuController.hideInputMethodMenuLocked();
@@ -1807,16 +1851,6 @@
     }
 
     @GuardedBy("ImfLock.class")
-    void clearInputShownLocked() {
-        mVisibilityStateComputer.setInputShown(false);
-    }
-
-    @GuardedBy("ImfLock.class")
-    private boolean isInputShownLocked() {
-        return mVisibilityStateComputer.isInputShown();
-    }
-
-    @GuardedBy("ImfLock.class")
     private boolean isShowRequestedForCurrentWindow(@UserIdInt int userId) {
         final var userData = getUserData(userId);
         // TODO(b/349904272): Make mVisibilityStateComputer multi-user aware
@@ -2589,7 +2623,7 @@
         if (!mShowOngoingImeSwitcherForPhones) return false;
         // When the IME switcher dialog is shown, the IME switcher button should be hidden.
         // TODO(b/305849394): Make mMenuController multi-user aware.
-        final boolean switcherMenuShowing = Flags.imeSwitcherRevamp()
+        final boolean switcherMenuShowing = mNewInputMethodSwitcherMenuEnabled
                 ? mMenuControllerNew.isShowing()
                 : mMenuController.getSwitchingDialogLocked() != null;
         if (switcherMenuShowing) {
@@ -2609,7 +2643,8 @@
                 || (visibility & InputMethodService.IME_INVISIBLE) != 0) {
             return false;
         }
-        if (mWindowManagerInternal.isHardKeyboardAvailable() && !Flags.imeSwitcherRevamp()) {
+        if (mWindowManagerInternal.isHardKeyboardAvailable()
+                && !mNewInputMethodSwitcherMenuEnabled) {
             // When physical keyboard is attached, we show the ime switcher (or notification if
             // NavBar is not available) because SHOW_IME_WITH_HARD_KEYBOARD settings currently
             // exists in the IME switcher dialog.  Might be OK to remove this condition once
@@ -2620,7 +2655,7 @@
         }
 
         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
-        if (Flags.imeSwitcherRevamp()) {
+        if (mNewInputMethodSwitcherMenuEnabled) {
             // The IME switcher button should be shown when the current IME specified a
             // language settings activity.
             final var curImi = settings.getMethodMap().get(settings.getSelectedInputMethod());
@@ -2739,20 +2774,18 @@
             if (targetWindow != null) {
                 mWindowManagerInternal.updateInputMethodTargetWindow(token, targetWindow);
             }
-            mLastImeTargetWindow = targetWindow;
+            mVisibilityStateComputer.setLastImeTargetWindow(targetWindow);
         }
     }
 
-    private void updateImeWindowStatus(boolean disableImeIcon) {
-        synchronized (ImfLock.class) {
-            // TODO(b/350386877): Propagate userId from the caller.
-            final int userId = mCurrentUserId;
-            if (disableImeIcon) {
-                final var bindingController = getInputMethodBindingController(userId);
-                updateSystemUiLocked(0, bindingController.getBackDisposition(), userId);
-            } else {
-                updateSystemUiLocked(userId);
-            }
+    @GuardedBy("ImfLock.class")
+    private void updateImeWindowStatusLocked(boolean disableImeIcon, int displayId) {
+        final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
+        if (disableImeIcon) {
+            final var bindingController = getInputMethodBindingController(userId);
+            updateSystemUiLocked(0, bindingController.getBackDisposition(), userId);
+        } else {
+            updateSystemUiLocked(userId);
         }
     }
 
@@ -2798,7 +2831,7 @@
             }
             final var curId = bindingController.getCurId();
             // TODO(b/305849394): Make mMenuController multi-user aware.
-            final boolean switcherMenuShowing = Flags.imeSwitcherRevamp()
+            final boolean switcherMenuShowing = mNewInputMethodSwitcherMenuEnabled
                     ? mMenuControllerNew.isShowing()
                     : mMenuController.getSwitchingDialogLocked() != null;
             if (switcherMenuShowing
@@ -2820,7 +2853,7 @@
     @GuardedBy("ImfLock.class")
     void updateFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId) {
         updateInputMethodsFromSettingsLocked(enabledMayChange, userId);
-        if (!Flags.imeSwitcherRevamp()) {
+        if (!mNewInputMethodSwitcherMenuEnabled) {
             mMenuController.updateKeyboardFromSettingsLocked();
         }
     }
@@ -3051,7 +3084,7 @@
         try {
             if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
             if (Flags.refactorInsetsController()) {
-                boolean wasVisible = isInputShownLocked();
+                boolean wasVisible = mVisibilityStateComputer.isInputShown();
                 if (userData.mImeBindingState != null
                         && userData.mImeBindingState.mFocusedWindowClient != null
                         && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
@@ -3081,8 +3114,7 @@
         ImeTracing.getInstance().triggerManagerServiceDump(
                 "InputMethodManagerService#showSoftInput", mDumper);
         synchronized (ImfLock.class) {
-            // TODO(b/305849394): Infer userId from windowToken
-            final int userId = mCurrentUserId;
+            final int userId = resolveImeUserIdFromWindowLocked(windowToken);
             final long ident = Binder.clearCallingIdentity();
             try {
                 if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
@@ -3102,8 +3134,7 @@
         ImeTracing.getInstance().triggerManagerServiceDump(
                 "InputMethodManagerService#hideSoftInput", mDumper);
         synchronized (ImfLock.class) {
-            // TODO(b/305849394): Infer userId from windowToken
-            final int userId = mCurrentUserId;
+            final int userId = resolveImeUserIdFromWindowLocked(windowToken);
             final long ident = Binder.clearCallingIdentity();
             try {
                 if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
@@ -3365,14 +3396,14 @@
             Objects.requireNonNull(windowToken, "windowToken must not be null");
             synchronized (ImfLock.class) {
                 Boolean windowPerceptible = mFocusedWindowPerceptible.get(windowToken);
-                final int userId = mCurrentUserId;
+                final int userId = resolveImeUserIdFromWindowLocked(windowToken);
                 final var userData = getUserData(userId);
                 if (userData.mImeBindingState.mFocusedWindow != windowToken
                         || (windowPerceptible != null && windowPerceptible == perceptible)) {
                     return;
                 }
                 mFocusedWindowPerceptible.put(windowToken, windowPerceptible);
-                updateSystemUiLocked(mCurrentUserId);
+                updateSystemUiLocked(userId);
             }
         });
     }
@@ -3488,7 +3519,7 @@
         final int callingUserId = UserHandle.getUserId(uid);
         final int userId = resolveImeUserIdLocked(callingUserId);
         if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken, userId)) {
-            if (isInputShownLocked()) {
+            if (mVisibilityStateComputer.isInputShown()) {
                 ImeTracker.forLogging().onFailed(
                         statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
             } else {
@@ -3506,7 +3537,7 @@
                 if (userData.mImeBindingState != null
                         && userData.mImeBindingState.mFocusedWindowClient != null
                         && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
-                    boolean wasVisible = isInputShownLocked();
+                    boolean wasVisible = mVisibilityStateComputer.isInputShown();
                     // TODO add windowToken to interface
                     userData.mImeBindingState.mFocusedWindowClient.mClient
                             .setImeVisibility(false, statsToken);
@@ -3568,7 +3599,7 @@
         // TODO(b/246309664): Clean up IMMS#mImeWindowVis
         IInputMethodInvoker curMethod = bindingController.getCurMethod();
         final boolean shouldHideSoftInput = curMethod != null
-                && (isInputShownLocked()
+                && (mVisibilityStateComputer.isInputShown()
                 || (bindingController.getImeWindowVis() & InputMethodService.IME_ACTIVE) != 0);
 
         mVisibilityStateComputer.requestImeVisibility(windowToken, false);
@@ -3977,7 +4008,7 @@
     @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
     public boolean isInputMethodPickerShownForTest() {
         synchronized (ImfLock.class) {
-            return Flags.imeSwitcherRevamp()
+            return mNewInputMethodSwitcherMenuEnabled
                     ? mMenuControllerNew.isShowing()
                     : mMenuController.isisInputMethodPickerShownForTestLocked();
         }
@@ -4056,7 +4087,7 @@
     @Override
     public void onImeSwitchButtonClickFromSystem(int displayId) {
         synchronized (ImfLock.class) {
-            final int userId = mCurrentUserId;
+            final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
             final var userData = getUserData(userId);
             final var bindingController = userData.mBindingController;
             final var curToken = bindingController.getCurToken();
@@ -4665,8 +4696,8 @@
             proto.write(CUR_SEQ, bindingController.getSequenceNumber());
             proto.write(CUR_CLIENT, Objects.toString(userData.mCurClient));
             userData.mImeBindingState.dumpDebug(proto, mWindowManagerInternal);
-            proto.write(LAST_IME_TARGET_WINDOW_NAME,
-                    mWindowManagerInternal.getWindowName(mLastImeTargetWindow));
+            proto.write(LAST_IME_TARGET_WINDOW_NAME, mWindowManagerInternal.getWindowName(
+                    mVisibilityStateComputer.getLastImeTargetWindow()));
             proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE, InputMethodDebug.softInputModeToString(
                     userData.mImeBindingState.mFocusedWindowSoftInputMode));
             if (userData.mCurEditorInfo != null) {
@@ -4683,7 +4714,7 @@
             proto.write(IS_INTERACTIVE, mIsInteractive);
             proto.write(BACK_DISPOSITION, bindingController.getBackDisposition());
             proto.write(IME_WINDOW_VISIBILITY, bindingController.getImeWindowVis());
-            if (!Flags.imeSwitcherRevamp()) {
+            if (!mNewInputMethodSwitcherMenuEnabled) {
                 proto.write(SHOW_IME_WITH_HARD_KEYBOARD,
                         mMenuController.getShowImeWithHardKeyboard());
             }
@@ -4840,8 +4871,8 @@
                                     .setImeVisibility(false, statsToken);
                         }
                     } else {
-                        hideCurrentInputLocked(mLastImeTargetWindow, statsToken, flags,
-                                null /* resultReceiver */, reason, userId);
+                        hideCurrentInputLocked(mVisibilityStateComputer.getLastImeTargetWindow(),
+                                statsToken, flags, null /* resultReceiver */, reason, userId);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -4879,9 +4910,9 @@
                                     .setImeVisibility(true, statsToken);
                         }
                     } else {
-                        showCurrentInputLocked(mLastImeTargetWindow, statsToken, flags,
-                                MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, reason,
-                                userId);
+                        showCurrentInputLocked(mVisibilityStateComputer.getLastImeTargetWindow(),
+                                statsToken, flags, MotionEvent.TOOL_TYPE_UNKNOWN,
+                                null /* resultReceiver */, reason, userId);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -4901,8 +4932,7 @@
     @GuardedBy("ImfLock.class")
     void onApplyImeVisibilityFromComputerLocked(IBinder windowToken,
             @NonNull ImeTracker.Token statsToken, @NonNull ImeVisibilityResult result) {
-        // TODO(b/305849394): Infer userId from windowToken
-        final int userId = mCurrentUserId;
+        final int userId = resolveImeUserIdFromWindowLocked(windowToken);
         mVisibilityApplier.applyImeVisibility(windowToken, statsToken, result.getState(),
                 result.getReason(), userId);
     }
@@ -4974,7 +5004,7 @@
                         // implemented so that auxiliary subtypes will be excluded when the soft
                         // keyboard is invisible.
                         synchronized (ImfLock.class) {
-                            showAuxSubtypes = isInputShownLocked();
+                            showAuxSubtypes = mVisibilityStateComputer.isInputShown();
                         }
                         break;
                     case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES:
@@ -5009,7 +5039,7 @@
                         return false;
                     }
 
-                    if (Flags.imeSwitcherRevamp()) {
+                    if (mNewInputMethodSwitcherMenuEnabled) {
                         if (DEBUG) {
                             Slog.v(TAG, "Show IME switcher menu,"
                                     + " showAuxSubtypes=" + showAuxSubtypes
@@ -5078,8 +5108,7 @@
             case MSG_REMOVE_IME_SURFACE_FROM_WINDOW: {
                 IBinder windowToken = (IBinder) msg.obj;
                 synchronized (ImfLock.class) {
-                    // TODO(b/305849394): Infer userId from windowToken.
-                    final int userId = mCurrentUserId;
+                    final int userId = resolveImeUserIdFromWindowLocked(windowToken);
                     final var userData = getUserData(userId);
                     try {
                         if (windowToken == userData.mImeBindingState.mFocusedWindow
@@ -5092,10 +5121,6 @@
                 }
                 return true;
             }
-            case MSG_UPDATE_IME_WINDOW_STATUS: {
-                updateImeWindowStatus(msg.arg1 == 1);
-                return true;
-            }
 
             // ---------------------------------------------------------
 
@@ -5105,7 +5130,7 @@
 
             // --------------------------------------------------------------
             case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
-                if (!Flags.imeSwitcherRevamp()) {
+                if (!mNewInputMethodSwitcherMenuEnabled) {
                     mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1);
                 }
                 synchronized (ImfLock.class) {
@@ -5883,8 +5908,7 @@
         @Override
         public void reportImeControl(@Nullable IBinder windowToken) {
             synchronized (ImfLock.class) {
-                // TODO(b/305849394): Need to infer userId or get userId from callers.
-                final int userId = mCurrentUserId;
+                final int userId = resolveImeUserIdFromWindowLocked(windowToken);
                 final var userData = getUserData(userId);
                 if (userData.mImeBindingState.mFocusedWindow != windowToken) {
                     // A perceptible value was set for the focused window, but it is no longer in
@@ -5899,14 +5923,14 @@
         @Override
         public void onImeParentChanged(int displayId) {
             synchronized (ImfLock.class) {
-                // TODO(b/305849394): Need to infer userId or get userId from callers.
-                final int userId = mCurrentUserId;
+                final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
                 final var userData = getUserData(userId);
                 // Hide the IME method menu only when the IME surface parent is changed by the
                 // input target changed, in case seeing the dialog dismiss flickering during
                 // the next focused window starting the input connection.
-                if (mLastImeTargetWindow != userData.mImeBindingState.mFocusedWindow) {
-                    if (Flags.imeSwitcherRevamp()) {
+                if (mVisibilityStateComputer.getLastImeTargetWindow()
+                        != userData.mImeBindingState.mFocusedWindow) {
+                    if (mNewInputMethodSwitcherMenuEnabled) {
                         final var bindingController = getInputMethodBindingController(userId);
                         mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
                     } else {
@@ -5925,8 +5949,11 @@
         @ImfLockFree
         @Override
         public void updateImeWindowStatus(boolean disableImeIcon, int displayId) {
-            mHandler.obtainMessage(MSG_UPDATE_IME_WINDOW_STATUS, disableImeIcon ? 1 : 0, 0)
-                    .sendToTarget();
+            mHandler.post(() -> {
+                synchronized (ImfLock.class) {
+                    updateImeWindowStatusLocked(disableImeIcon, displayId);
+                }
+            });
         }
 
         @Override
@@ -6024,8 +6051,8 @@
         public void onSwitchKeyboardLayoutShortcut(int direction, int displayId,
                 IBinder targetWindowToken) {
             synchronized (ImfLock.class) {
-                // TODO(b/305849394): Infer userId from displayId
-                switchKeyboardLayoutLocked(direction, getUserData(mCurrentUserId));
+                final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
+                switchKeyboardLayoutLocked(direction, getUserData(userId));
             }
         }
     }
@@ -6259,7 +6286,7 @@
                     };
             mUserDataRepository.forAllUserData(userDataDump);
 
-            if (Flags.imeSwitcherRevamp()) {
+            if (mNewInputMethodSwitcherMenuEnabled) {
                 p.println("  menuControllerNew:");
                 mMenuControllerNew.dump(p, "  ");
             } else {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
index 1b84036..4f5af63 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -19,15 +19,13 @@
 import android.annotation.AnyThread;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
 
 final class InputMethodSettingsRepository {
-    // TODO(b/352594784): Should we user other lock primitives?
-    @GuardedBy("sPerUserMap")
+    private static final Object sMutationLock = new Object();
+
     @NonNull
-    private static final SparseArray<InputMethodSettings> sPerUserMap = new SparseArray<>();
+    private static volatile ImmutableSparseArray<InputMethodSettings> sPerUserMap =
+            ImmutableSparseArray.empty();
 
     /**
      * Not intended to be instantiated.
@@ -38,10 +36,7 @@
     @NonNull
     @AnyThread
     static InputMethodSettings get(@UserIdInt int userId) {
-        final InputMethodSettings obj;
-        synchronized (sPerUserMap) {
-            obj = sPerUserMap.get(userId);
-        }
+        final InputMethodSettings obj = sPerUserMap.get(userId);
         if (obj != null) {
             return obj;
         }
@@ -50,15 +45,15 @@
 
     @AnyThread
     static void put(@UserIdInt int userId, @NonNull InputMethodSettings obj) {
-        synchronized (sPerUserMap) {
-            sPerUserMap.put(userId, obj);
+        synchronized (sMutationLock) {
+            sPerUserMap = sPerUserMap.cloneWithPutOrSelf(userId, obj);
         }
     }
 
     @AnyThread
     static void remove(@UserIdInt int userId) {
-        synchronized (sPerUserMap) {
-            sPerUserMap.remove(userId);
+        synchronized (sMutationLock) {
+            sPerUserMap = sPerUserMap.cloneWithRemoveOrSelf(userId);
         }
     }
 }
diff --git a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
index e7cff20..b3500be 100644
--- a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
+++ b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
@@ -24,7 +24,6 @@
 import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.LocalServices;
@@ -38,6 +37,13 @@
  * to the persistent value when the user storage is unlocked.</p>
  */
 final class SecureSettingsWrapper {
+
+    private static final Object sMutationLock = new Object();
+
+    @NonNull
+    private static volatile ImmutableSparseArray<ReaderWriter> sUserMap =
+            ImmutableSparseArray.empty();
+
     @Nullable
     private static volatile ContentResolver sContentResolver = null;
 
@@ -61,8 +67,8 @@
      */
     @AnyThread
     static void endTestMode() {
-        synchronized (sUserMap) {
-            sUserMap.clear();
+        synchronized (sMutationLock) {
+            sUserMap = ImmutableSparseArray.empty();
         }
         sTestMode = false;
     }
@@ -243,10 +249,6 @@
         }
     }
 
-    @GuardedBy("sUserMap")
-    @NonNull
-    private static final SparseArray<ReaderWriter> sUserMap = new SparseArray<>();
-
     private static final ReaderWriter NOOP = new ReaderWriter() {
         @Override
         public void putString(String key, String str) {
@@ -282,15 +284,15 @@
     private static ReaderWriter putOrGet(@UserIdInt int userId,
             @NonNull ReaderWriter readerWriter) {
         final boolean isUnlockedUserImpl = readerWriter instanceof UnlockedUserImpl;
-        synchronized (sUserMap) {
+        synchronized (sMutationLock) {
             final ReaderWriter current = sUserMap.get(userId);
             if (current == null) {
-                sUserMap.put(userId, readerWriter);
+                sUserMap = sUserMap.cloneWithPutOrSelf(userId, readerWriter);
                 return readerWriter;
             }
             // Upgrading from CopyOnWriteImpl to DirectImpl is allowed.
             if (current instanceof LockedUserImpl && isUnlockedUserImpl) {
-                sUserMap.put(userId, readerWriter);
+                sUserMap = sUserMap.cloneWithPutOrSelf(userId, readerWriter);
                 return readerWriter;
             }
             return current;
@@ -300,11 +302,9 @@
     @NonNull
     @AnyThread
     private static ReaderWriter get(@UserIdInt int userId) {
-        synchronized (sUserMap) {
-            final ReaderWriter readerWriter = sUserMap.get(userId);
-            if (readerWriter != null) {
-                return readerWriter;
-            }
+        final ReaderWriter readerWriter = sUserMap.get(userId);
+        if (readerWriter != null) {
+            return readerWriter;
         }
         if (sTestMode) {
             return putOrGet(userId, new FakeReaderWriterImpl());
@@ -363,8 +363,8 @@
      */
     @AnyThread
     static void onUserRemoved(@UserIdInt int userId) {
-        synchronized (sUserMap) {
-            sUserMap.remove(userId);
+        synchronized (sMutationLock) {
+            sUserMap = sUserMap.cloneWithRemoveOrSelf(userId);
         }
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 6f831cc..e3524b1 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -19,51 +19,39 @@
 import android.annotation.AnyThread;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
-import android.util.SparseArray;
 
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.function.Consumer;
 import java.util.function.IntFunction;
 
 final class UserDataRepository {
 
-    private final ReentrantReadWriteLock mUserDataLock = new ReentrantReadWriteLock();
+    private final Object mMutationLock = new Object();
 
-    @GuardedBy("mUserDataLock")
-    private final SparseArray<UserData> mUserData = new SparseArray<>();
+    @NonNull
+    private volatile ImmutableSparseArray<UserData> mUserData = ImmutableSparseArray.empty();
 
     private final IntFunction<InputMethodBindingController> mBindingControllerFactory;
 
     @AnyThread
     @NonNull
     UserData getOrCreate(@UserIdInt int userId) {
-        mUserDataLock.writeLock().lock();
-        try {
-            UserData userData = mUserData.get(userId);
-            if (userData == null) {
-                userData = new UserData(userId, mBindingControllerFactory.apply(userId));
-                mUserData.put(userId, userData);
-            }
+        // Do optimistic read first for optimization.
+        final var userData = mUserData.get(userId);
+        if (userData != null) {
             return userData;
-        } finally {
-            mUserDataLock.writeLock().unlock();
+        }
+        // Note that the below line can be called concurrently. Here we assume that
+        // instantiating UserData for the same user multiple times would have no side effect.
+        final var newUserData = new UserData(userId, mBindingControllerFactory.apply(userId));
+        synchronized (mMutationLock) {
+            mUserData = mUserData.cloneWithPutOrSelf(userId, newUserData);
+            return newUserData;
         }
     }
 
     @AnyThread
     void forAllUserData(Consumer<UserData> consumer) {
-        final SparseArray<UserData> copiedArray;
-        mUserDataLock.readLock().lock();
-        try {
-            copiedArray = mUserData.clone();
-        } finally {
-            mUserDataLock.readLock().unlock();
-        }
-        for (int i = 0; i < copiedArray.size(); i++) {
-            consumer.accept(copiedArray.valueAt(i));
-        }
+        mUserData.forEach(consumer);
     }
 
     UserDataRepository(
@@ -73,11 +61,8 @@
 
     @AnyThread
     void remove(@UserIdInt int userId) {
-        mUserDataLock.writeLock().lock();
-        try {
-            mUserData.remove(userId);
-        } finally {
-            mUserDataLock.writeLock().unlock();
+        synchronized (mMutationLock) {
+            mUserData = mUserData.cloneWithRemoveOrSelf(userId);
         }
     }
 }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 8d3f07e..a17c48d 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -3264,6 +3264,7 @@
     /**
      * Tries to restore the disabled system package after an update has been deleted.
      */
+    @GuardedBy("mPm.mInstallLock")
     public void restoreDisabledSystemPackageLIF(DeletePackageAction action,
             @NonNull int[] allUserHandles, boolean writeSettings) throws SystemDeleteException {
         final PackageSetting deletedPs = action.mDeletingPs;
@@ -3282,10 +3283,21 @@
         }
         // Install the system package
         if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
-        try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
+        try {
             final int[] origUsers = outInfo == null ? null : outInfo.mOrigUsers;
-            installPackageFromSystemLIF(disabledPs.getPathString(), allUserHandles,
-                    origUsers, writeSettings);
+            try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
+                installPackageFromSystemLIF(disabledPs.getPathString(), allUserHandles,
+                        origUsers, writeSettings);
+            }
+            if (origUsers != null) {
+                mPm.commitPackageStateMutation(null, mutator -> {
+                    for (int userId : origUsers) {
+                        mutator.forPackage(disabledPs.getPackageName())
+                                .userState(userId)
+                                .setOverlayPaths(deletedPs.getOverlayPaths(userId));
+                    }
+                });
+            }
         } catch (PackageManagerException e) {
             Slog.w(TAG, "Failed to restore system package:" + deletedPs.getPackageName() + ": "
                     + e.getMessage());
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index bca81f52..c21f783 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -64,6 +64,7 @@
 import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN;
 import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
 import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller;
+import static com.android.server.stats.Flags.applyNetworkStatsPollRateLimit;
 import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
 import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
 import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines;
@@ -298,6 +299,13 @@
      */
     private static final long NETSTATS_UID_DEFAULT_BUCKET_DURATION_MS = HOURS.toMillis(2);
 
+    /**
+     * Polling NetworkStats is a heavy operation and it should be done sparingly. Atom pulls may
+     * happen in bursts, but these should be infrequent. The poll rate limit ensures that data is
+     * sufficiently fresh (i.e. not stale) while reducing system load during atom pull bursts.
+     */
+    private static final long NETSTATS_POLL_RATE_LIMIT_MS = 15000;
+
     private static final int CPU_TIME_PER_THREAD_FREQ_MAX_NUM_FREQUENCIES = 8;
     private static final int OP_FLAGS_PULLED = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED;
     private static final String COMMON_PERMISSION_PREFIX = "android.permission.";
@@ -415,6 +423,9 @@
     @GuardedBy("mDataBytesTransferLock")
     private final ArrayList<NetworkStatsExt> mNetworkStatsBaselines = new ArrayList<>();
 
+    @GuardedBy("mDataBytesTransferLock")
+    private long mLastNetworkStatsPollTime = -NETSTATS_POLL_RATE_LIMIT_MS;
+
     // Listener for monitoring subscriptions changed event.
     private StatsSubscriptionsListener mStatsSubscriptionsListener;
     // List that stores SubInfo of subscriptions that ever appeared since boot.
@@ -1063,24 +1074,26 @@
         // Initialize NetworkStats baselines.
         synchronized (mDataBytesTransferLock) {
             mNetworkStatsBaselines.addAll(
-                    collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.WIFI_BYTES_TRANSFER));
+                    collectNetworkStatsSnapshotForAtomLocked(
+                            FrameworkStatsLog.WIFI_BYTES_TRANSFER));
             mNetworkStatsBaselines.addAll(
-                    collectNetworkStatsSnapshotForAtom(
+                    collectNetworkStatsSnapshotForAtomLocked(
                             FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG));
             mNetworkStatsBaselines.addAll(
-                    collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.MOBILE_BYTES_TRANSFER));
-            mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
+                    collectNetworkStatsSnapshotForAtomLocked(
+                            FrameworkStatsLog.MOBILE_BYTES_TRANSFER));
+            mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtomLocked(
                     FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG));
-            mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
+            mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtomLocked(
                     FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED));
             mNetworkStatsBaselines.addAll(
-                    collectNetworkStatsSnapshotForAtom(
+                    collectNetworkStatsSnapshotForAtomLocked(
                             FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER));
             mNetworkStatsBaselines.addAll(
-                    collectNetworkStatsSnapshotForAtom(
+                    collectNetworkStatsSnapshotForAtomLocked(
                             FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER));
             if (canQueryTypeProxy) {
-                mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
+                mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtomLocked(
                         FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG));
             }
         }
@@ -1243,12 +1256,14 @@
         );
     }
 
+    @GuardedBy("mDataBytesTransferLock")
     @NonNull
-    private List<NetworkStatsExt> collectNetworkStatsSnapshotForAtom(int atomTag) {
+    private List<NetworkStatsExt> collectNetworkStatsSnapshotForAtomLocked(int atomTag) {
         List<NetworkStatsExt> ret = new ArrayList<>();
         switch (atomTag) {
             case FrameworkStatsLog.WIFI_BYTES_TRANSFER: {
-                final NetworkStats stats = getUidNetworkStatsSnapshotForTransport(TRANSPORT_WIFI);
+                final NetworkStats stats = getUidNetworkStatsSnapshotForTransportLocked(
+                        TRANSPORT_WIFI);
                 if (stats != null) {
                     ret.add(new NetworkStatsExt(sliceNetworkStatsByUid(stats),
                             new int[]{TRANSPORT_WIFI}, /*slicedByFgbg=*/false));
@@ -1256,7 +1271,8 @@
                 break;
             }
             case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: {
-                final NetworkStats stats = getUidNetworkStatsSnapshotForTransport(TRANSPORT_WIFI);
+                final NetworkStats stats = getUidNetworkStatsSnapshotForTransportLocked(
+                        TRANSPORT_WIFI);
                 if (stats != null) {
                     ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
                             new int[]{TRANSPORT_WIFI}, /*slicedByFgbg=*/true));
@@ -1265,7 +1281,7 @@
             }
             case FrameworkStatsLog.MOBILE_BYTES_TRANSFER: {
                 final NetworkStats stats =
-                        getUidNetworkStatsSnapshotForTransport(TRANSPORT_CELLULAR);
+                        getUidNetworkStatsSnapshotForTransportLocked(TRANSPORT_CELLULAR);
                 if (stats != null) {
                     ret.add(new NetworkStatsExt(sliceNetworkStatsByUid(stats),
                             new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/false));
@@ -1274,7 +1290,7 @@
             }
             case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: {
                 final NetworkStats stats =
-                        getUidNetworkStatsSnapshotForTransport(TRANSPORT_CELLULAR);
+                        getUidNetworkStatsSnapshotForTransportLocked(TRANSPORT_CELLULAR);
                 if (stats != null) {
                     ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
                             new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true));
@@ -1282,7 +1298,7 @@
                 break;
             }
             case FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG: {
-                final NetworkStats stats = getUidNetworkStatsSnapshotForTemplate(
+                final NetworkStats stats = getUidNetworkStatsSnapshotForTemplateLocked(
                         new NetworkTemplate.Builder(MATCH_PROXY).build(),  /*includeTags=*/false);
                 if (stats != null) {
                     ret.add(new NetworkStatsExt(sliceNetworkStatsByUidTagAndMetered(stats),
@@ -1294,9 +1310,9 @@
                 break;
             }
             case FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED: {
-                final NetworkStats wifiStats = getUidNetworkStatsSnapshotForTemplate(
+                final NetworkStats wifiStats = getUidNetworkStatsSnapshotForTemplateLocked(
                         new NetworkTemplate.Builder(MATCH_WIFI).build(), /*includeTags=*/true);
-                final NetworkStats cellularStats = getUidNetworkStatsSnapshotForTemplate(
+                final NetworkStats cellularStats = getUidNetworkStatsSnapshotForTemplateLocked(
                         new NetworkTemplate.Builder(MATCH_MOBILE)
                                 .setMeteredness(METERED_YES).build(), /*includeTags=*/true);
                 if (wifiStats != null && cellularStats != null) {
@@ -1311,12 +1327,12 @@
             }
             case FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER: {
                 for (final SubInfo subInfo : mHistoricalSubs) {
-                    ret.addAll(getDataUsageBytesTransferSnapshotForSub(subInfo));
+                    ret.addAll(getDataUsageBytesTransferSnapshotForSubLocked(subInfo));
                 }
                 break;
             }
             case FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER: {
-                ret.addAll(getDataUsageBytesTransferSnapshotForOemManaged());
+                ret.addAll(getDataUsageBytesTransferSnapshotForOemManagedLocked());
                 break;
             }
             default:
@@ -1325,8 +1341,9 @@
         return ret;
     }
 
+    @GuardedBy("mDataBytesTransferLock")
     private int pullDataBytesTransferLocked(int atomTag, @NonNull List<StatsEvent> pulledData) {
-        final List<NetworkStatsExt> current = collectNetworkStatsSnapshotForAtom(atomTag);
+        final List<NetworkStatsExt> current = collectNetworkStatsSnapshotForAtomLocked(atomTag);
 
         if (current == null) {
             Slog.e(TAG, "current snapshot is null for " + atomTag + ", return.");
@@ -1459,8 +1476,9 @@
         }
     }
 
+    @GuardedBy("mDataBytesTransferLock")
     @NonNull
-    private List<NetworkStatsExt> getDataUsageBytesTransferSnapshotForOemManaged() {
+    private List<NetworkStatsExt> getDataUsageBytesTransferSnapshotForOemManagedLocked() {
         final List<Pair<Integer, Integer>> matchRulesAndTransports = List.of(
                 new Pair(MATCH_ETHERNET, TRANSPORT_ETHERNET),
                 new Pair(MATCH_MOBILE, TRANSPORT_CELLULAR),
@@ -1479,7 +1497,8 @@
                 // Thus, specifying networks through their identifiers are not needed.
                 final NetworkTemplate template = new NetworkTemplate.Builder(matchRule)
                         .setOemManaged(oemManaged).build();
-                final NetworkStats stats = getUidNetworkStatsSnapshotForTemplate(template, false);
+                final NetworkStats stats = getUidNetworkStatsSnapshotForTemplateLocked(
+                        template, false);
                 final Integer transport = ruleAndTransport.second;
                 if (stats != null) {
                     ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
@@ -1496,8 +1515,9 @@
     /**
      * Create a snapshot of NetworkStats for a given transport.
      */
+    @GuardedBy("mDataBytesTransferLock")
     @Nullable
-    private NetworkStats getUidNetworkStatsSnapshotForTransport(int transport) {
+    private NetworkStats getUidNetworkStatsSnapshotForTransportLocked(int transport) {
         NetworkTemplate template = null;
         switch (transport) {
             case TRANSPORT_CELLULAR:
@@ -1510,7 +1530,7 @@
             default:
                 Log.wtf(TAG, "Unexpected transport.");
         }
-        return getUidNetworkStatsSnapshotForTemplate(template, /*includeTags=*/false);
+        return getUidNetworkStatsSnapshotForTemplateLocked(template, /*includeTags=*/false);
     }
 
     /**
@@ -1534,8 +1554,9 @@
      * Note that this should be only used to calculate diff since the snapshot might contains
      * some traffic before boot.
      */
+    @GuardedBy("mDataBytesTransferLock")
     @Nullable
-    private NetworkStats getUidNetworkStatsSnapshotForTemplate(
+    private NetworkStats getUidNetworkStatsSnapshotForTemplateLocked(
             @NonNull NetworkTemplate template, boolean includeTags) {
         final long elapsedMillisSinceBoot = SystemClock.elapsedRealtime();
         final long currentTimeInMillis = MICROSECONDS.toMillis(SystemClock.currentTimeMicro());
@@ -1547,13 +1568,19 @@
         final long startTime = currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration;
         final long endTime = currentTimeInMillis + bucketDuration;
 
-        // TODO (b/156313635): This is short-term hack to allow perfd gets updated networkStats
-        //  history when query in every second in order to show realtime statistics. However,
-        //  this is not a good long-term solution since NetworkStatsService will make frequent
-        //  I/O and also block main thread when polling.
-        //  Consider making perfd queries NetworkStatsService directly.
-        if (template.getMatchRule() == MATCH_WIFI && template.getSubscriberIds().isEmpty()) {
-            getNetworkStatsManager().forceUpdate();
+        // NetworkStatsManager#forceUpdate updates stats for all networks
+        if (applyNetworkStatsPollRateLimit()) {
+            // The new way: rate-limit force-polling for all NetworkStats queries
+            if (elapsedMillisSinceBoot - mLastNetworkStatsPollTime >= NETSTATS_POLL_RATE_LIMIT_MS) {
+                mLastNetworkStatsPollTime = elapsedMillisSinceBoot;
+                getNetworkStatsManager().forceUpdate();
+            }
+        } else {
+            // The old way: force-poll only on WiFi queries. Data for other queries can be stale
+            // if there was no recent poll beforehand (e.g. for WiFi or scheduled poll)
+            if (template.getMatchRule() == MATCH_WIFI && template.getSubscriberIds().isEmpty()) {
+                getNetworkStatsManager().forceUpdate();
+            }
         }
 
         final android.app.usage.NetworkStats queryNonTaggedStats =
@@ -1572,8 +1599,9 @@
         return nonTaggedStats.add(taggedStats);
     }
 
+    @GuardedBy("mDataBytesTransferLock")
     @NonNull
-    private List<NetworkStatsExt> getDataUsageBytesTransferSnapshotForSub(
+    private List<NetworkStatsExt> getDataUsageBytesTransferSnapshotForSubLocked(
             @NonNull SubInfo subInfo) {
         final List<NetworkStatsExt> ret = new ArrayList<>();
         for (final int ratType : getAllCollapsedRatTypes()) {
@@ -1583,7 +1611,7 @@
                             .setRatType(ratType)
                             .setMeteredness(METERED_YES).build();
             final NetworkStats stats =
-                    getUidNetworkStatsSnapshotForTemplate(template, /*includeTags=*/false);
+                    getUidNetworkStatsSnapshotForTemplateLocked(template, /*includeTags=*/false);
             if (stats != null) {
                 ret.add(new NetworkStatsExt(sliceNetworkStatsByFgbg(stats),
                         new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true,
@@ -5273,6 +5301,13 @@
 
         @Override
         public void onSubscriptionsChanged() {
+            synchronized (mDataBytesTransferLock) {
+                onSubscriptionsChangedLocked();
+            }
+        }
+
+        @GuardedBy("mDataBytesTransferLock")
+        private void onSubscriptionsChangedLocked() {
             final List<SubscriptionInfo> currentSubs = mSm.getCompleteActiveSubscriptionInfoList();
             for (final SubscriptionInfo sub : currentSubs) {
                 final SubInfo match = CollectionUtils.find(mHistoricalSubs,
@@ -5295,12 +5330,11 @@
                         subscriberId, sub.isOpportunistic());
                 Slog.i(TAG, "subId " + subId + " added into historical sub list");
 
-                synchronized (mDataBytesTransferLock) {
-                    mHistoricalSubs.add(subInfo);
-                    // Since getting snapshot when pulling will also include data before boot,
-                    // query stats as baseline to prevent double count is needed.
-                    mNetworkStatsBaselines.addAll(getDataUsageBytesTransferSnapshotForSub(subInfo));
-                }
+                mHistoricalSubs.add(subInfo);
+                // Since getting snapshot when pulling will also include data before boot,
+                // query stats as baseline to prevent double count is needed.
+                mNetworkStatsBaselines.addAll(
+                        getDataUsageBytesTransferSnapshotForSubLocked(subInfo));
             }
         }
     }
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
index 6faa273..f360837 100644
--- a/services/core/java/com/android/server/stats/stats_flags.aconfig
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -8,3 +8,11 @@
     bug: "309512867"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "apply_network_stats_poll_rate_limit"
+    namespace: "statsd"
+    description: "Apply a rate limit for polling network stats when pulling relevant atoms"
+    bug: "352495181"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
index aa2b74e..58c3ba5 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
+++ b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
@@ -56,12 +56,17 @@
         // enables immediate failover to a secondary provider, one that might provide valid IDs for
         // the same location, which should provide better behavior than just ignoring the event.
         if (hasInvalidZones(event)) {
-            TimeZoneProviderStatus providerStatus = new TimeZoneProviderStatus.Builder(
-                    event.getTimeZoneProviderStatus())
-                    .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
-                    .build();
-            return TimeZoneProviderEvent.createUncertainEvent(
-                    event.getCreationElapsedMillis(), providerStatus);
+            TimeZoneProviderStatus providerStatus = event.getTimeZoneProviderStatus();
+            TimeZoneProviderStatus.Builder providerStatusBuilder;
+            if (providerStatus != null) {
+                providerStatusBuilder = new TimeZoneProviderStatus.Builder(providerStatus);
+            } else {
+                providerStatusBuilder = new TimeZoneProviderStatus.Builder();
+            }
+            return TimeZoneProviderEvent.createUncertainEvent(event.getCreationElapsedMillis(),
+                    providerStatusBuilder
+                            .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
+                            .build());
         }
 
         return event;
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index 4da6585..de5e662 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -41,6 +41,7 @@
 import android.os.SystemClock;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
 import android.util.IndentingPrintWriter;
 import android.util.IntArray;
 import android.util.Slog;
@@ -265,9 +266,15 @@
                 return null;
             }
 
+            if (Flags.throttleVibrationParamsRequests() && mVibrationParamRequest != null
+                    && mVibrationParamRequest.usage == usage) {
+                // Reuse existing future for ongoing request with same usage.
+                return mVibrationParamRequest.future;
+            }
+
             try {
                 endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
-                mVibrationParamRequest = new VibrationParamRequest(uid);
+                mVibrationParamRequest = new VibrationParamRequest(uid, usage);
                 vibratorController.requestVibrationParams(vibrationType, timeoutInMillis,
                         mVibrationParamRequest.token);
                 return mVibrationParamRequest.future;
@@ -533,10 +540,12 @@
         public final CompletableFuture<Void> future = new CompletableFuture<>();
         public final IBinder token = new Binder();
         public final int uid;
+        public final @VibrationAttributes.Usage int usage;
         public final long uptimeMs;
 
-        VibrationParamRequest(int uid) {
+        VibrationParamRequest(int uid, @VibrationAttributes.Usage int usage) {
             this.uid = uid;
+            this.usage = usage;
             uptimeMs = SystemClock.uptimeMillis();
         }
 
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index ba74f50..59435b8 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -456,10 +456,6 @@
         }
     }
 
-    InputChannel getInputChannel() {
-        return mInputInterceptor == null ? null : mInputInterceptor.mClientChannel;
-    }
-
     InputWindowHandle getInputWindowHandle() {
         return mInputInterceptor == null ? null : mInputInterceptor.mDragWindowHandle;
     }
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 872b4e1..3d07744 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -60,7 +60,6 @@
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.window.flags.Flags;
 
 import java.io.PrintWriter;
 
@@ -198,14 +197,6 @@
             setWakeTransitionReady();
             return;
         }
-        EventLogTags.writeWmSetKeyguardShown(
-                displayId,
-                keyguardShowing ? 1 : 0,
-                aodShowing ? 1 : 0,
-                state.mKeyguardGoingAway ? 1 : 0,
-                state.mOccluded ? 1 : 0,
-                "setKeyguardShown");
-
         // Update the task snapshot if the screen will not be turned off. To make sure that the
         // unlocking animation can animate consistent content. The conditions are:
         // - Either AOD or keyguard changes to be showing. So if the states change individually,
@@ -224,6 +215,7 @@
 
         state.mKeyguardShowing = keyguardShowing;
         state.mAodShowing = aodShowing;
+        state.writeEventLog("setKeyguardShown");
 
         if (keyguardChanged) {
             // Irrelevant to AOD.
@@ -232,19 +224,13 @@
                 state.mDismissalRequested = false;
             }
             if (goingAwayRemoved
-                    || (Flags.keyguardAppearTransition() && keyguardShowing
-                            && !Display.isOffState(dc.getDisplayInfo().state))) {
+                    || (keyguardShowing && !Display.isOffState(dc.getDisplayInfo().state))) {
                 // Keyguard decided to show or stopped going away. Send a transition to animate back
                 // to the locked state before holding the sleep token again
-                final DisplayContent transitionDc = Flags.keyguardAppearTransition()
-                        ? dc
-                        : mRootWindowContainer.getDefaultDisplay();
-                transitionDc.requestTransitionAndLegacyPrepare(
+                dc.requestTransitionAndLegacyPrepare(
                         TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING);
-                if (Flags.keyguardAppearTransition()) {
-                    dc.mWallpaperController.adjustWallpaperWindows();
-                }
-                transitionDc.executeAppTransition();
+                dc.mWallpaperController.adjustWallpaperWindows();
+                dc.executeAppTransition();
             }
         }
 
@@ -284,13 +270,7 @@
         mService.deferWindowLayout();
         state.mKeyguardGoingAway = true;
         try {
-            EventLogTags.writeWmSetKeyguardShown(
-                    displayId,
-                    state.mKeyguardShowing ? 1 : 0,
-                    state.mAodShowing ? 1 : 0,
-                    1 /* keyguardGoingAway */,
-                    state.mOccluded ? 1 : 0,
-                    "keyguardGoingAway");
+            state.writeEventLog("keyguardGoingAway");
             final int transitFlags = convertTransitFlags(flags);
             final DisplayContent dc = mRootWindowContainer.getDefaultDisplay();
             dc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, transitFlags);
@@ -646,6 +626,16 @@
             mSleepTokenAcquirer.release(mDisplayId);
         }
 
+        void writeEventLog(String reason) {
+            EventLogTags.writeWmSetKeyguardShown(
+                    mDisplayId,
+                    mKeyguardShowing ? 1 : 0,
+                    mAodShowing ? 1 : 0,
+                    mKeyguardGoingAway ? 1 : 0,
+                    mOccluded ? 1 : 0,
+                    reason);
+        }
+
         /**
          * Updates keyguard status if the top task could be visible. The top task may occlude
          * keyguard, request to dismiss keyguard or make insecure keyguard go away based on its
@@ -716,18 +706,11 @@
 
             boolean hasChange = false;
             if (lastOccluded != mOccluded) {
-                if (mDisplayId == DEFAULT_DISPLAY) {
-                    EventLogTags.writeWmSetKeyguardShown(
-                            mDisplayId,
-                            mKeyguardShowing ? 1 : 0,
-                            mAodShowing ? 1 : 0,
-                            mKeyguardGoingAway ? 1 : 0,
-                            mOccluded ? 1 : 0,
-                            "updateVisibility");
-                }
+                writeEventLog("updateVisibility");
                 controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity);
                 hasChange = true;
             } else if (!lastKeyguardGoingAway && mKeyguardGoingAway) {
+                writeEventLog("dismissIfInsecure");
                 controller.handleKeyguardGoingAwayChanged(display);
                 hasChange = true;
             }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ab1e969..1c9aaf9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2287,7 +2287,7 @@
         // Apply crop to root tasks only and clear the crops of the descendant tasks.
         int width = 0;
         int height = 0;
-        if (isRootTask()) {
+        if (isRootTask() && !mTransitionController.mIsWaitingForDisplayEnabled) {
             final Rect taskBounds = getBounds();
             width = taskBounds.width();
             height = taskBounds.height();
@@ -3413,6 +3413,7 @@
         info.isSleeping = shouldSleepActivities();
         info.isTopActivityTransparent = top != null && !top.fillsParent();
         info.isTopActivityStyleFloating = top != null && top.isStyleFloating();
+        info.lastNonFullscreenBounds = topTask.mLastNonFullscreenBounds;
         appCompatTaskInfo.topActivityLetterboxVerticalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
         appCompatTaskInfo.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
         appCompatTaskInfo.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET;
diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
index 34b9913..2c58c61 100644
--- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
@@ -97,10 +97,10 @@
 
     /**
      * @return If a window animation has outsets applied to it.
-     * @see Animation#hasExtension()
+     * @see Animation#getExtensionEdges()
      */
     public boolean hasExtension() {
-        return mAnimation.hasExtension();
+        return mAnimation.getExtensionEdges() != 0;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 88e8064..c6aaf4e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -380,7 +380,7 @@
                 InputChannel source) {
             return state.register(display)
                 .thenApply(unused ->
-                    service.startDragAndDrop(source.getToken(), state.getInputChannel()));
+                    service.startDragAndDrop(source.getToken(), state.getInputToken()));
         }
 
         /**
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
index faa198c..8981b7a 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
@@ -251,7 +251,7 @@
         synchronized (ImfLock.class) {
             // Assume the last IME targeted window has requested IME visible
             final IBinder lastImeTargetWindowToken = new Binder();
-            mInputMethodManagerService.mLastImeTargetWindow = lastImeTargetWindowToken;
+            mComputer.setLastImeTargetWindow(lastImeTargetWindowToken);
             mComputer.requestImeVisibility(lastImeTargetWindowToken, true);
             final ImeTargetWindowState lastState = mComputer.getWindowStateOrNull(
                     lastImeTargetWindowToken);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImmutableSparseArrayTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImmutableSparseArrayTest.java
new file mode 100644
index 0000000..944b7c6
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImmutableSparseArrayTest.java
@@ -0,0 +1,278 @@
+/*
+ * 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.server.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.annotation.NonNull;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+public final class ImmutableSparseArrayTest {
+
+    @Test
+    public void testEmptyObject() {
+        final ImmutableSparseArray<Object> empty = ImmutableSparseArray.empty();
+
+        assertThat(empty.size()).isEqualTo(0);
+        verifyCommonBehaviors(empty);
+    }
+
+    @Test
+    public void testEmptyMethod() {
+        assertThat(ImmutableSparseArray.empty()).isSameInstanceAs(ImmutableSparseArray.empty());
+    }
+
+    @Test
+    public void testCloneWithPutOrSelf_appendingFromEmpty() {
+        final int key1 = 1;
+        final Object value1 = new Object();
+        final int key2 = -2;  // intentionally negative
+        final Object value2 = new Object();
+        final int key3 = -3;  // intentionally negative
+        final Object value3 = new Object();
+        final int key4 = 4;
+        final Object value4 = new Object();
+
+        final ImmutableSparseArray<Object> oneItemArray = ImmutableSparseArray.empty()
+                .cloneWithPutOrSelf(key1, value1);
+        verifyCommonBehaviors(oneItemArray);
+        assertThat(oneItemArray.size()).isEqualTo(1);
+        assertThat(oneItemArray.get(key1)).isSameInstanceAs(value1);
+
+        final ImmutableSparseArray<Object> twoItemArray =
+                oneItemArray.cloneWithPutOrSelf(key2, value2);
+        assertThat(twoItemArray).isNotSameInstanceAs(oneItemArray);
+        verifyCommonBehaviors(twoItemArray);
+        assertThat(twoItemArray.size()).isEqualTo(2);
+        assertThat(twoItemArray.get(key1)).isSameInstanceAs(value1);
+        assertThat(twoItemArray.get(key2)).isSameInstanceAs(value2);
+
+        final ImmutableSparseArray<Object> threeItemArray =
+                twoItemArray.cloneWithPutOrSelf(key3, value3);
+        assertThat(threeItemArray).isNotSameInstanceAs(twoItemArray);
+        verifyCommonBehaviors(threeItemArray);
+        assertThat(threeItemArray.size()).isEqualTo(3);
+        assertThat(threeItemArray.get(key1)).isSameInstanceAs(value1);
+        assertThat(threeItemArray.get(key2)).isSameInstanceAs(value2);
+        assertThat(threeItemArray.get(key3)).isSameInstanceAs(value3);
+
+        final ImmutableSparseArray<Object> fourItemArray =
+                threeItemArray.cloneWithPutOrSelf(key4, value4);
+        assertThat(fourItemArray).isNotSameInstanceAs(threeItemArray);
+        verifyCommonBehaviors(fourItemArray);
+        assertThat(fourItemArray.size()).isEqualTo(4);
+        assertThat(fourItemArray.get(key1)).isSameInstanceAs(value1);
+        assertThat(fourItemArray.get(key2)).isSameInstanceAs(value2);
+        assertThat(fourItemArray.get(key3)).isSameInstanceAs(value3);
+        assertThat(fourItemArray.get(key4)).isSameInstanceAs(value4);
+    }
+
+    @Test
+    public void testCloneWithPutOrSelf_returnSelf() {
+        final int key1 = 1;
+        final Object value1 = new Object();
+        final ImmutableSparseArray<Object> array = ImmutableSparseArray
+                .empty()
+                .cloneWithPutOrSelf(key1, value1);
+        assertThat(array.cloneWithPutOrSelf(key1, value1)).isSameInstanceAs(array);
+    }
+
+    @Test
+    public void testCloneWithPutOrSelf_updateExistingValue() {
+        final int key1 = 1;
+        final Object value1 = new Object();
+        final int key2 = 2;
+        final Object value2 = new Object();
+        final Object value2updated = new Object();
+        final int key3 = 3;
+        final Object value3 = new Object();
+
+        final ImmutableSparseArray<Object> array = ImmutableSparseArray
+                .empty()
+                .cloneWithPutOrSelf(key1, value1)
+                .cloneWithPutOrSelf(key2, value2)
+                .cloneWithPutOrSelf(key3, value3);
+
+        final var updatedArray = array.cloneWithPutOrSelf(key2, value2updated);
+        verifyCommonBehaviors(updatedArray);
+
+        assertThat(updatedArray.size()).isEqualTo(3);
+        assertThat(updatedArray.get(key1)).isSameInstanceAs(value1);
+        assertThat(updatedArray.get(key2)).isSameInstanceAs(value2updated);
+        assertThat(updatedArray.get(key3)).isSameInstanceAs(value3);
+    }
+
+    @Test
+    public void testCloneWithRemoveOrSelf_empty() {
+        final ImmutableSparseArray<Object> empty = ImmutableSparseArray.empty();
+        assertThat(empty.cloneWithRemoveOrSelf(0)).isSameInstanceAs(empty);
+    }
+
+    @Test
+    public void testCloneWithRemoveOrSelf_singleInstance() {
+        final int key = 1;
+        final Object value = new Object();
+        final ImmutableSparseArray<Object> array = ImmutableSparseArray
+                .empty()
+                .cloneWithPutOrSelf(key, value);
+        assertThat(array.cloneWithRemoveOrSelf(key)).isSameInstanceAs(ImmutableSparseArray.empty());
+    }
+
+    @Test
+    public void testCloneWithRemoveOrSelf_firstItem() {
+        final int key1 = 1;
+        final Object value1 = new Object();
+        final int key2 = 2;
+        final Object value2 = new Object();
+        final int key3 = 3;
+        final Object value3 = new Object();
+
+        final ImmutableSparseArray<Object> array = ImmutableSparseArray
+                .empty()
+                .cloneWithPutOrSelf(key1, value1)
+                .cloneWithPutOrSelf(key2, value2)
+                .cloneWithPutOrSelf(key3, value3)
+                .cloneWithRemoveOrSelf(key1);
+        verifyCommonBehaviors(array);
+
+        assertThat(array.size()).isEqualTo(2);
+        assertThat(array.get(key1)).isNull();
+        assertThat(array.get(key2)).isSameInstanceAs(value2);
+        assertThat(array.get(key3)).isSameInstanceAs(value3);
+        assertThat(array.keyAt(0)).isEqualTo(key2);
+        assertThat(array.keyAt(1)).isEqualTo(key3);
+    }
+
+    @Test
+    public void testCloneWithRemoveOrSelf_lastItem() {
+        final int key1 = 1;
+        final Object value1 = new Object();
+        final int key2 = 2;
+        final Object value2 = new Object();
+        final int key3 = 3;
+        final Object value3 = new Object();
+
+        final ImmutableSparseArray<Object> array = ImmutableSparseArray
+                .empty()
+                .cloneWithPutOrSelf(key1, value1)
+                .cloneWithPutOrSelf(key2, value2)
+                .cloneWithPutOrSelf(key3, value3)
+                .cloneWithRemoveOrSelf(key3);
+        verifyCommonBehaviors(array);
+
+        assertThat(array.size()).isEqualTo(2);
+        assertThat(array.get(key1)).isSameInstanceAs(value1);
+        assertThat(array.get(key2)).isSameInstanceAs(value2);
+        assertThat(array.get(key3)).isNull();
+    }
+
+    @Test
+    public void testCloneWithRemoveOrSelf_middleItem() {
+        final int key1 = 1;
+        final Object value1 = new Object();
+        final int key2 = 2;
+        final Object value2 = new Object();
+        final int key3 = 3;
+        final Object value3 = new Object();
+
+        final ImmutableSparseArray<Object> array = ImmutableSparseArray
+                .empty()
+                .cloneWithPutOrSelf(key1, value1)
+                .cloneWithPutOrSelf(key2, value2)
+                .cloneWithPutOrSelf(key3, value3)
+                .cloneWithRemoveOrSelf(key2);
+        verifyCommonBehaviors(array);
+
+        assertThat(array.size()).isEqualTo(2);
+        assertThat(array.get(key1)).isSameInstanceAs(value1);
+        assertThat(array.get(key2)).isNull();
+        assertThat(array.get(key3)).isSameInstanceAs(value3);
+    }
+
+    @Test
+    public void testCloneWithRemoveOrSelf_nonExistentItem() {
+        final int key1 = 1;
+        final Object value1 = new Object();
+        final int key2 = 2;
+        final Object value2 = new Object();
+        final int key3 = 3;
+        final Object value3 = new Object();
+        final int key4 = 4;
+
+        final ImmutableSparseArray<Object> array = ImmutableSparseArray
+                .empty()
+                .cloneWithPutOrSelf(key1, value1)
+                .cloneWithPutOrSelf(key2, value2)
+                .cloneWithPutOrSelf(key3, value3);
+
+        assertThat(array.cloneWithRemoveOrSelf(key4)).isSameInstanceAs(array);
+    }
+
+    @Test
+    public void testForEach() {
+        final int key1 = 1;
+        final Object value1 = new Object();
+        final int key2 = 2;
+        final Object value2 = new Object();
+        final int key3 = 3;
+        final Object value3 = new Object();
+
+        final ImmutableSparseArray<Object> array = ImmutableSparseArray
+                .empty()
+                .cloneWithPutOrSelf(key1, value1)
+                .cloneWithPutOrSelf(key2, value2)
+                .cloneWithPutOrSelf(key3, value3);
+
+        final ArrayList<Object> list = new ArrayList<>();
+        array.forEach(list::add);
+        assertThat(list).containsExactlyElementsIn(new Object[]{ value1, value2, value3 })
+                .inOrder();
+    }
+
+
+    private void verifyCommonBehaviors(@NonNull ImmutableSparseArray<Object> sparseArray) {
+        verifyInvalidKeyBehaviors(sparseArray);
+        verifyOutOfBoundsBehaviors(sparseArray);
+    }
+
+    private void verifyInvalidKeyBehaviors(@NonNull ImmutableSparseArray<Object> sparseArray) {
+        final int invalid_key = -123456678;
+        assertThat(sparseArray.get(invalid_key)).isNull();
+        assertThat(sparseArray.indexOfKey(invalid_key)).isEqualTo(-1);
+    }
+
+    private void verifyOutOfBoundsBehaviors(@NonNull ImmutableSparseArray<Object> sparseArray) {
+        final int size = sparseArray.size();
+        assertThrows(ArrayIndexOutOfBoundsException.class,
+                () -> sparseArray.keyAt(size));
+        assertThrows(ArrayIndexOutOfBoundsException.class,
+                () -> sparseArray.valueAt(size));
+        assertThrows(ArrayIndexOutOfBoundsException.class,
+                () -> sparseArray.keyAt(-1));
+        assertThrows(ArrayIndexOutOfBoundsException.class,
+                () -> sparseArray.valueAt(-1));
+    }
+}
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 ec9bfa7..d427a6d 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -272,7 +272,7 @@
 
         // Certain tests rely on TEST_IME_ID that is installed with AndroidTest.xml.
         // TODO(b/352615651): Consider just synthesizing test InputMethodInfo then injecting it.
-        AdditionalSubtypeMapRepository.ensureInitializedAndGet(mCallingUserId);
+        AdditionalSubtypeMapRepository.initializeIfNecessary(mCallingUserId);
         final var settings = InputMethodManagerService.queryInputMethodServicesInternal(mContext,
                 mCallingUserId, AdditionalSubtypeMapRepository.get(mCallingUserId),
                 DirectBootAwareness.AUTO);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java
index f92eaab..c1b3929 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java
@@ -18,13 +18,13 @@
 
 import static com.android.server.accessibility.Flags.FLAG_ENABLE_A11Y_CHECKER_LOGGING;
 import static com.android.server.accessibility.a11ychecker.AccessibilityCheckerConstants.MIN_DURATION_BETWEEN_CHECKS;
+import static com.android.server.accessibility.a11ychecker.TestUtils.QUALIFIED_TEST_ACTIVITY_NAME;
 import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_CLASS_NAME;
 import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME;
 import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_ACTIVITY_NAME;
 import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_DEFAULT_BROWSER;
 import static com.android.server.accessibility.a11ychecker.TestUtils.createAtom;
 import static com.android.server.accessibility.a11ychecker.TestUtils.getMockPackageManagerWithInstalledApps;
-import static com.android.server.accessibility.a11ychecker.TestUtils.getTestAccessibilityEvent;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -114,7 +114,7 @@
 
         Set<A11yCheckerProto.AccessibilityCheckResultReported> results =
                 mAccessibilityCheckerManager.maybeRunA11yChecker(
-                        List.of(mockNodeInfo1, mockNodeInfo2), getTestAccessibilityEvent(),
+                        List.of(mockNodeInfo1, mockNodeInfo2), QUALIFIED_TEST_ACTIVITY_NAME,
                         new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
                                 TEST_A11Y_SERVICE_CLASS_NAME), /*userId=*/ 0);
 
@@ -139,7 +139,7 @@
 
         Set<A11yCheckerProto.AccessibilityCheckResultReported> results =
                 mAccessibilityCheckerManager.maybeRunA11yChecker(
-                        List.of(mockNodeInfo), getTestAccessibilityEvent(),
+                        List.of(mockNodeInfo), QUALIFIED_TEST_ACTIVITY_NAME,
                         new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
                                 TEST_A11Y_SERVICE_CLASS_NAME), /*userId=*/ 0);
 
@@ -160,7 +160,7 @@
 
         Set<A11yCheckerProto.AccessibilityCheckResultReported> results =
                 mAccessibilityCheckerManager.maybeRunA11yChecker(
-                        List.of(mockNodeInfo, mockNodeInfoDuplicate), getTestAccessibilityEvent(),
+                        List.of(mockNodeInfo, mockNodeInfoDuplicate), QUALIFIED_TEST_ACTIVITY_NAME,
                         new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
                                 TEST_A11Y_SERVICE_CLASS_NAME), /*userId=*/ 0);
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
index 141f174..5b4e72e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
@@ -16,11 +16,13 @@
 
 package com.android.server.accessibility.a11ychecker;
 
+import static com.android.server.accessibility.a11ychecker.TestUtils.QUALIFIED_TEST_ACTIVITY_NAME;
 import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_CLASS_NAME;
 import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_ACTIVITY_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_APP_PACKAGE_NAME;
 import static com.android.server.accessibility.a11ychecker.TestUtils.createAtom;
 import static com.android.server.accessibility.a11ychecker.TestUtils.getMockPackageManagerWithInstalledApps;
-import static com.android.server.accessibility.a11ychecker.TestUtils.getTestAccessibilityEvent;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -28,7 +30,6 @@
 
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
-import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -138,11 +139,15 @@
     }
 
     @Test
-    public void getActivityName_hasWindowStateChangedEvent_returnsActivityName() {
-        AccessibilityEvent accessibilityEvent = getTestAccessibilityEvent();
-
+    public void getActivityName_hasValidActivityClassName_returnsActivityName() {
         assertThat(AccessibilityCheckerUtils.getActivityName(mMockPackageManager,
-                accessibilityEvent)).isEqualTo("MainActivity");
+                TEST_APP_PACKAGE_NAME, QUALIFIED_TEST_ACTIVITY_NAME)).isEqualTo(TEST_ACTIVITY_NAME);
+    }
+
+    @Test
+    public void getActivityName_hasInvalidActivityClassName_returnsActivityName() {
+        assertThat(AccessibilityCheckerUtils.getActivityName(mMockPackageManager,
+                TEST_APP_PACKAGE_NAME, "com.NonActivityClass")).isEmpty();
     }
 
     // Makes sure the AccessibilityHierarchyCheck class to enum mapping is up to date with the
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java
index ec1a255..acf64b6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.server.accessibility.a11ychecker;
 
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.when;
 
@@ -26,6 +27,7 @@
 import android.content.pm.PackageManager;
 import android.view.accessibility.AccessibilityEvent;
 
+import org.mockito.AdditionalMatchers;
 import org.mockito.Mockito;
 
 public class TestUtils {
@@ -46,8 +48,11 @@
         ComponentName testActivityComponentName = new ComponentName(TEST_APP_PACKAGE_NAME,
                 QUALIFIED_TEST_ACTIVITY_NAME);
 
-        when(mockPackageManager.getActivityInfo(testActivityComponentName, 0))
+        when(mockPackageManager.getActivityInfo(eq(testActivityComponentName), eq(0)))
                 .thenReturn(testActivityInfo);
+        when(mockPackageManager.getActivityInfo(
+                AdditionalMatchers.not(eq(testActivityComponentName)), eq(0)))
+                .thenThrow(PackageManager.NameNotFoundException.class);
         when(mockPackageManager.getPackageInfo(TEST_APP_PACKAGE_NAME, 0))
                 .thenReturn(createPackageInfo(TEST_APP_PACKAGE_NAME, TEST_APP_VERSION_CODE,
                         testActivityInfo));
diff --git a/services/tests/timetests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java b/services/tests/timetests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
index f3440f7..ea3b409 100644
--- a/services/tests/timetests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
+++ b/services/tests/timetests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
@@ -39,13 +39,23 @@
 
     private static final long ARBITRARY_TIME_MILLIS = 11223344;
 
+    private final List<String> mNonExistingTimeZones = Arrays.asList(
+            "SystemV/HST10", "Atlantic/Atlantis", "EUROPE/LONDON", "Etc/GMT-5:30");
     private final ZoneInfoDbTimeZoneProviderEventPreProcessor mPreProcessor =
             new ZoneInfoDbTimeZoneProviderEventPreProcessor();
 
+    private static final TimeZoneProviderStatus ARBITRARY_TIME_ZONE_PROVIDER_STATUS =
+            new TimeZoneProviderStatus.Builder()
+                    .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                    .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+                    .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
+                    .build();
+
     @Test
     public void timeZoneIdsFromZoneInfoDbAreValid() {
         for (String timeZone : TimeZone.getAvailableIDs()) {
-            TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone);
+            TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone,
+                    ARBITRARY_TIME_ZONE_PROVIDER_STATUS);
             assertWithMessage("Time zone %s should be supported", timeZone)
                     .that(mPreProcessor.preProcess(event)).isEqualTo(event);
         }
@@ -53,11 +63,9 @@
 
     @Test
     public void eventWithNonExistingZones_areMappedToUncertainEvent() {
-        List<String> nonExistingTimeZones = Arrays.asList(
-                "SystemV/HST10", "Atlantic/Atlantis", "EUROPE/LONDON", "Etc/GMT-5:30");
-
-        for (String timeZone : nonExistingTimeZones) {
-            TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone);
+        for (String timeZone : mNonExistingTimeZones) {
+            TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone,
+                    ARBITRARY_TIME_ZONE_PROVIDER_STATUS);
 
             TimeZoneProviderStatus expectedProviderStatus =
                     new TimeZoneProviderStatus.Builder(event.getTimeZoneProviderStatus())
@@ -73,14 +81,31 @@
         }
     }
 
-    private static TimeZoneProviderEvent timeZoneProviderEvent(String... timeZoneIds) {
-        TimeZoneProviderStatus providerStatus = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
-                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
-                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
-                .build();
+    @Test
+    public void eventWithNullProviderStatus_areMappedToUncertainEvent() {
+        for (String timeZone : mNonExistingTimeZones) {
+            TimeZoneProviderEvent eventWithNullStatus = timeZoneProviderEvent(timeZone,
+                    /* providerStatus= */ null);
+
+            TimeZoneProviderStatus expectedProviderStatus =
+                    new TimeZoneProviderStatus.Builder()
+                            .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
+                            .build();
+
+            TimeZoneProviderEvent expectedResultEvent =
+                    TimeZoneProviderEvent.createUncertainEvent(
+                            eventWithNullStatus.getCreationElapsedMillis(),
+                            expectedProviderStatus);
+            assertWithMessage(timeZone + " with null time zone provider status")
+                    .that(mPreProcessor.preProcess(eventWithNullStatus))
+                    .isEqualTo(expectedResultEvent);
+        }
+    }
+
+    private static TimeZoneProviderEvent timeZoneProviderEvent(String timeZoneId,
+            TimeZoneProviderStatus providerStatus) {
         TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
-                .setTimeZoneIds(Arrays.asList(timeZoneIds))
+                .setTimeZoneIds(Arrays.asList(timeZoneId))
                 .setElapsedRealtimeMillis(ARBITRARY_TIME_MILLIS)
                 .build();
         return TimeZoneProviderEvent.createSuggestionEvent(
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 8ca8623..c496bbb 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -45,6 +45,11 @@
 import android.os.IBinder;
 import android.os.Process;
 import android.os.test.TestLooper;
+import android.os.vibrator.Flags;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
@@ -63,7 +68,6 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
 
 public class VibratorControlServiceTest {
 
@@ -71,6 +75,8 @@
 
     @Rule
     public MockitoRule rule = MockitoJUnit.rule();
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Mock private VibrationScaler mMockVibrationScaler;
     @Mock private PackageManagerInternal mPackageManagerInternalMock;
@@ -98,6 +104,7 @@
         mVibratorControlService = new VibratorControlService(
                 InstrumentationRegistry.getContext(), new VibratorControllerHolder(),
                 mMockVibrationScaler, mVibrationSettings, mStatsLoggerMock, mLock);
+        mFakeVibratorController.setVibratorControlService(mVibratorControlService);
     }
 
     @Test
@@ -280,10 +287,10 @@
         CompletableFuture<Void> future =
                 mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
                         timeoutInMillis);
-        try {
-            future.orTimeout(timeoutInMillis, TimeUnit.MILLISECONDS).get();
-        } catch (Throwable ignored) {
-        }
+        mTestLooper.dispatchAll();
+
+        assertThat(future).isNotNull();
+        assertThat(future.isDone()).isTrue();
         assertThat(mFakeVibratorController.didRequestVibrationParams).isTrue();
         assertThat(mFakeVibratorController.requestVibrationType).isEqualTo(
                 ScaleParam.TYPE_RINGTONE);
@@ -315,6 +322,46 @@
         }
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_THROTTLE_VIBRATION_PARAMS_REQUESTS)
+    public void testRequestVibrationParams_withOngoingRequestAndSameUsage_returnOngoingFuture() {
+        int timeoutInMillis = 10;
+        mVibratorControlService.registerVibratorController(mFakeVibratorController);
+        CompletableFuture<Void> future =
+                mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
+                        timeoutInMillis);
+        CompletableFuture<Void> future2 =
+                mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
+                        timeoutInMillis);
+        mTestLooper.dispatchAll();
+
+        assertThat(future).isNotNull();
+        assertThat(future).isEqualTo(future2);
+        assertThat(future.isDone()).isTrue();
+        assertThat(mFakeVibratorController.requestVibrationParamsCounter).isEqualTo(1);
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_THROTTLE_VIBRATION_PARAMS_REQUESTS)
+    public void testRequestVibrationParams_withOngoingRequestAndSameUsage_returnNewFuture() {
+        int timeoutInMillis = 10;
+        mVibratorControlService.registerVibratorController(mFakeVibratorController);
+        CompletableFuture<Void> future =
+                mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
+                        timeoutInMillis);
+        CompletableFuture<Void> future2 =
+                mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
+                        timeoutInMillis);
+        mTestLooper.dispatchAll();
+
+        assertThat(future).isNotNull();
+        assertThat(future2).isNotNull();
+        assertThat(future).isNotEqualTo(future2);
+        assertThat(future.isDone()).isTrue();
+        assertThat(future2.isDone()).isTrue();
+        assertThat(mFakeVibratorController.requestVibrationParamsCounter).isEqualTo(2);
+    }
+
     private static int buildVibrationTypesMask(int... types) {
         int typesMask = 0;
         for (int type : types) {
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
index 0cd88ef..c0e1407 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
@@ -39,6 +39,7 @@
     public boolean didRequestVibrationParams = false;
     public int requestVibrationType = VibrationAttributes.USAGE_UNKNOWN;
     public long requestTimeoutInMillis = 0;
+    public int requestVibrationParamsCounter = 0;
 
     public FakeVibratorController(Looper looper) {
         mHandler = new Handler(looper);
@@ -58,6 +59,7 @@
         didRequestVibrationParams = true;
         requestVibrationType = vibrationType;
         requestTimeoutInMillis = timeoutInMillis;
+        requestVibrationParamsCounter++;
         mHandler.post(() -> {
             if (mVibratorControlService != null) {
                 mVibratorControlService.onRequestVibrationParamsComplete(token, mRequestResult);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index e565006..1072ef0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -179,7 +179,7 @@
         mWindow = createDropTargetWindow("Drag test window", 0);
         doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0);
         when(mWm.mInputManager.startDragAndDrop(any(IBinder.class),
-                any(InputChannel.class))).thenReturn(true);
+                any(IBinder.class))).thenReturn(true);
 
         mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
     }
@@ -707,7 +707,7 @@
                     .setFormat(PixelFormat.TRANSLUCENT)
                     .build();
 
-            assertTrue(mWm.mInputManager.startDragAndDrop(new Binder(), new InputChannel()));
+            assertTrue(mWm.mInputManager.startDragAndDrop(new Binder(), new Binder()));
             mToken = mTarget.performDrag(TEST_PID, 0, mWindow.mClient,
                     flag, surface, 0, 0, 0, 0, 0, 0, 0, data);
             assertNotNull(mToken);
diff --git a/wifi/wifi.aconfig b/wifi/wifi.aconfig
index f7162f6..5a214b7 100644
--- a/wifi/wifi.aconfig
+++ b/wifi/wifi.aconfig
@@ -35,3 +35,14 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "hotspot_network_connecting_state_for_details_page"
+    namespace: "wifi"
+    description: "Update getConnectedState in HotspotNetworkEntry so that details page displays correctly."
+    bug: "321096462"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}