Merge "Fix ZenModeRepositoryTest flag flipping" into main
diff --git a/BAL_OWNERS b/BAL_OWNERS
index d56a1d4..ec779e7 100644
--- a/BAL_OWNERS
+++ b/BAL_OWNERS
@@ -2,4 +2,6 @@
 achim@google.com
 topjohnwu@google.com
 lus@google.com
+haok@google.com
+wnan@google.com
 
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index dfa7206..ee03e4b 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -120,6 +120,7 @@
 import android.os.ThreadLocalWorkSource;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.WorkSource;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
@@ -1794,7 +1795,8 @@
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
 
         mUseFrozenStateToDropListenerAlarms = Flags.useFrozenStateToDropListenerAlarms();
-        mStartUserBeforeScheduledAlarms = Flags.startUserBeforeScheduledAlarms();
+        mStartUserBeforeScheduledAlarms = Flags.startUserBeforeScheduledAlarms()
+                && UserManager.supportsMultipleUsers();
         if (mStartUserBeforeScheduledAlarms) {
             mUserWakeupStore = new UserWakeupStore();
             mUserWakeupStore.init();
@@ -3015,7 +3017,7 @@
                     mUseFrozenStateToDropListenerAlarms);
             pw.println();
             pw.print(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS,
-                    mStartUserBeforeScheduledAlarms);
+                    Flags.startUserBeforeScheduledAlarms());
             pw.decreaseIndent();
             pw.println();
             pw.println();
diff --git a/api/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt
index bf67187..0a3ae4f 100644
--- a/api/coverage/tools/ExtractFlaggedApis.kt
+++ b/api/coverage/tools/ExtractFlaggedApis.kt
@@ -16,9 +16,9 @@
 
 package android.platform.coverage
 
+import com.android.tools.metalava.model.CallableItem
 import com.android.tools.metalava.model.ClassItem
 import com.android.tools.metalava.model.Item
-import com.android.tools.metalava.model.MethodItem
 import com.android.tools.metalava.model.text.ApiFile
 import java.io.File
 import java.io.FileWriter
@@ -40,24 +40,24 @@
 
 fun extractFlaggedApisFromClass(
     classItem: ClassItem,
-    methods: List<MethodItem>,
+    callables: List<CallableItem>,
     packageName: String,
     builder: FlagApiMap.Builder
 ) {
-    if (methods.isEmpty()) return
+    if (callables.isEmpty()) return
     val classFlag = getClassFlag(classItem)
-    for (method in methods) {
-        val methodFlag = getFlagAnnotation(method) ?: classFlag
+    for (callable in callables) {
+        val callableFlag = getFlagAnnotation(callable) ?: classFlag
         val api =
             JavaMethod.newBuilder()
                 .setPackageName(packageName)
                 .setClassName(classItem.fullName())
-                .setMethodName(method.name())
-        for (param in method.parameters()) {
+                .setMethodName(callable.name())
+        for (param in callable.parameters()) {
             api.addParameters(param.type().toTypeString())
         }
-        if (methodFlag != null) {
-            addFlaggedApi(builder, api, methodFlag)
+        if (callableFlag != null) {
+            addFlaggedApi(builder, api, callableFlag)
         }
     }
 }
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index 978a8f9..e3dbb2b 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -747,6 +747,10 @@
      * {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList}.
      * </p>
      *
+     * <p>This function returns an empty array if
+     * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}
+     * is not supported.</p>
+     *
      * @return an array of supported high speed video recording sizes
      * @see #getHighSpeedVideoFpsRangesFor(Size)
      * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
@@ -836,6 +840,10 @@
      * supported for the same recording rate.</li>
      * </p>
      *
+     * <p>This function returns an empty array if
+     * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}
+     * is not supported.</p>
+     *
      * @return an array of supported high speed video recording FPS ranges The upper bound of
      *         returned ranges is guaranteed to be larger or equal to 120.
      * @see #getHighSpeedVideoSizesFor
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index d560399..855c309 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -16,7 +16,6 @@
 
 package android.inputmethodservice;
 
-import static android.view.inputmethod.Flags.predictiveBackIme;
 import static android.inputmethodservice.InputMethodServiceProto.CANDIDATES_VIEW_STARTED;
 import static android.inputmethodservice.InputMethodServiceProto.CANDIDATES_VISIBILITY;
 import static android.inputmethodservice.InputMethodServiceProto.CONFIGURATION;
@@ -57,6 +56,7 @@
 import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED;
 import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING;
 import static android.view.inputmethod.Flags.ctrlShiftShortcut;
+import static android.view.inputmethod.Flags.predictiveBackIme;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
@@ -101,7 +101,6 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.Trace;
-import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.InputType;
 import android.text.Layout;
@@ -4348,7 +4347,7 @@
      * @hide
      */
     final void onImeSwitchButtonClickFromClient() {
-        mPrivOps.onImeSwitchButtonClickFromClient(getDisplayId(), UserHandle.myUserId());
+        mPrivOps.onImeSwitchButtonClickFromClient(getDisplayId());
     }
 
     /**
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
index 8b1577c..97993b6 100644
--- a/core/java/android/os/IVibratorManagerService.aidl
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -41,5 +41,5 @@
     // There is no order guarantee with respect to the two-way APIs above like
     // vibrate/isVibrating/cancel.
     oneway void performHapticFeedback(int uid, int deviceId, String opPkg, int constant,
-            boolean always, String reason, boolean fromIme);
+            String reason, int flags, int privFlags);
 }
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 2a62c24..5339d73 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -206,13 +206,12 @@
     }
 
     @Override
-    public void performHapticFeedback(
-            int constant, boolean always, String reason, boolean fromIme) {
+    public void performHapticFeedback(int constant, String reason, int flags, int privFlags) {
         if (mVibratorManager == null) {
             Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager.");
             return;
         }
-        mVibratorManager.performHapticFeedback(constant, always, reason, fromIme);
+        mVibratorManager.performHapticFeedback(constant, reason, flags, privFlags);
     }
 
     @Override
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index c80bcac..a9846ba 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -147,15 +147,14 @@
     }
 
     @Override
-    public void performHapticFeedback(int constant, boolean always, String reason,
-            boolean fromIme) {
+    public void performHapticFeedback(int constant, String reason, int flags, int privFlags) {
         if (mService == null) {
             Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager service.");
             return;
         }
         try {
-            mService.performHapticFeedback(
-                    mUid, mContext.getDeviceId(), mPackageName, constant, always, reason, fromIme);
+            mService.performHapticFeedback(mUid, mContext.getDeviceId(), mPackageName, constant,
+                    reason, flags, privFlags);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to perform haptic feedback.", e);
         }
@@ -245,9 +244,8 @@
         }
 
         @Override
-        public void performHapticFeedback(int effectId, boolean always, String reason,
-                boolean fromIme) {
-            SystemVibratorManager.this.performHapticFeedback(effectId, always, reason, fromIme);
+        public void performHapticFeedback(int effectId, String reason, int flags, int privFlags) {
+            SystemVibratorManager.this.performHapticFeedback(effectId, reason, flags, privFlags);
         }
 
         @Override
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 8af371c..71c83f2 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -33,6 +33,7 @@
 import android.os.vibrator.VibrationConfig;
 import android.os.vibrator.VibratorFrequencyProfile;
 import android.util.Log;
+import android.view.HapticFeedbackConstants;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -519,17 +520,15 @@
      *
      * @param constant the ID for the haptic feedback. This should be one of the constants defined
      *          in {@link HapticFeedbackConstants}.
-     * @param always {@code true} if the haptic feedback should be played regardless of the user
-     *          vibration intensity settings applicable to the corresponding vibration.
-     *          {@code false} if the vibration for the haptic feedback should respect the applicable
-     *          vibration intensity settings.
      * @param reason the reason for this haptic feedback.
-     * @param fromIme the haptic feedback is performed from an IME.
+     * @param flags Additional flags as per {@link HapticFeedbackConstants}.
+     * @param privFlags Additional private flags as per {@link HapticFeedbackConstants}.
      *
      * @hide
      */
-    public void performHapticFeedback(int constant, boolean always, String reason,
-            boolean fromIme) {
+    public void performHapticFeedback(int constant, String reason,
+            @HapticFeedbackConstants.Flags int flags,
+            @HapticFeedbackConstants.PrivateFlags int privFlags) {
         Log.w(TAG, "performHapticFeedback is not supported");
     }
 
diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java
index 513c4bd..2c7a852 100644
--- a/core/java/android/os/VibratorManager.java
+++ b/core/java/android/os/VibratorManager.java
@@ -23,6 +23,7 @@
 import android.app.ActivityThread;
 import android.content.Context;
 import android.util.Log;
+import android.view.HapticFeedbackConstants;
 
 /**
  * Provides access to all vibrators from the device, as well as the ability to run them
@@ -142,15 +143,14 @@
      *
      * @param constant the ID of the requested haptic feedback. Should be one of the constants
      *          defined in {@link HapticFeedbackConstants}.
-     * @param always {@code true} if the haptic feedback should be played regardless of the user
-     *          vibration intensity settings applicable to the corresponding vibration.
-     *          {@code false} otherwise.
      * @param reason the reason for this haptic feedback.
-     * @param fromIme the haptic feedback is performed from an IME.
+     * @param flags Additional flags as per {@link HapticFeedbackConstants}.
+     * @param privFlags Additional private flags as per {@link HapticFeedbackConstants}.
      * @hide
      */
-    public void performHapticFeedback(int constant, boolean always, String reason,
-            boolean fromIme) {
+    public void performHapticFeedback(int constant, String reason,
+            @HapticFeedbackConstants.Flags int flags,
+            @HapticFeedbackConstants.PrivateFlags int privFlags) {
         Log.w(TAG, "performHapticFeedback is not supported");
     }
 
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 69228ca..1fe06d4 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -16,11 +16,30 @@
 
 package android.view;
 
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Constants to be used to perform haptic feedback effects via
  * {@link View#performHapticFeedback(int)} 
  */
 public class HapticFeedbackConstants {
+    /** @hide **/
+    @IntDef(flag = true, prefix = "FLAG_", value = {
+            FLAG_IGNORE_VIEW_SETTING,
+            FLAG_IGNORE_GLOBAL_SETTING,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Flags {}
+
+    /** @hide **/
+    @IntDef(flag = true, prefix = "PRIVATE_FLAG_", value = {
+            PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PrivateFlags {}
 
     private HapticFeedbackConstants() {}
 
@@ -258,4 +277,14 @@
      */
     @Deprecated
     public static final int FLAG_IGNORE_GLOBAL_SETTING = 0x0002;
+
+    /**
+     * Flag for {@link android.os.Vibrator#performHapticFeedback(int, boolean, String, int, int)} or
+     * {@link ViewRootImpl#performHapticFeedback(int, boolean, int, int)}: Perform the haptic
+     * feedback with the input method vibration settings, e.g. applying the keyboard vibration
+     * user settings to the KEYBOARD_* constants.
+     *
+     * @hide
+     */
+    public static final int PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS = 0x0001;
 }
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 070d33b..14407ca 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -140,13 +140,13 @@
             int seqId);
 
     @UnsupportedAppUsage
-    boolean performHapticFeedback(int effectId, boolean always, boolean fromIme);
+    boolean performHapticFeedback(int effectId, int flags, int privFlags);
 
     /**
      * Called by attached views to perform predefined haptic feedback without requiring VIBRATE
      * permission.
      */
-    oneway void performHapticFeedbackAsync(int effectId, boolean always, boolean fromIme);
+    oneway void performHapticFeedbackAsync(int effectId, int flags, int privFlags);
 
     /**
      * Initiate the drag operation itself
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 88d7a83..2edbcc2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -28636,20 +28636,20 @@
             return false;
         }
 
-        final boolean always = (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0;
-        boolean fromIme = false;
-        if (mAttachInfo.mViewRootImpl != null) {
-            fromIme = mAttachInfo.mViewRootImpl.mWindowAttributes.type == TYPE_INPUT_METHOD;
+        int privFlags = 0;
+        if (mAttachInfo.mViewRootImpl != null
+                && mAttachInfo.mViewRootImpl.mWindowAttributes.type == TYPE_INPUT_METHOD) {
+            privFlags = HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS;
         }
         if (Flags.useVibratorHapticFeedback()) {
             if (!mAttachInfo.canPerformHapticFeedback()) {
                 return false;
             }
-            getSystemVibrator().performHapticFeedback(
-                    feedbackConstant, always, "View#performHapticFeedback", fromIme);
+            getSystemVibrator().performHapticFeedback(feedbackConstant,
+                    "View#performHapticFeedback", flags, privFlags);
             return true;
         }
-        return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, always, fromIme);
+        return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, flags, privFlags);
     }
 
     private Vibrator getSystemVibrator() {
@@ -31684,7 +31684,10 @@
 
         interface Callbacks {
             void playSoundEffect(int effectId);
-            boolean performHapticFeedback(int effectId, boolean always, boolean fromIme);
+
+            boolean performHapticFeedback(int effectId,
+                    @HapticFeedbackConstants.Flags int flags,
+                    @HapticFeedbackConstants.PrivateFlags int privFlags);
         }
 
         /**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e5b17c8..596726f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -9666,18 +9666,18 @@
      * {@inheritDoc}
      */
     @Override
-    public boolean performHapticFeedback(int effectId, boolean always, boolean fromIme) {
+    public boolean performHapticFeedback(int effectId, int flags, int privFlags) {
         if ((mDisplay.getFlags() & Display.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
             return false;
         }
 
         try {
             if (USE_ASYNC_PERFORM_HAPTIC_FEEDBACK) {
-                mWindowSession.performHapticFeedbackAsync(effectId, always, fromIme);
+                mWindowSession.performHapticFeedbackAsync(effectId, flags, privFlags);
                 return true;
             } else {
                 // Original blocking binder call path.
-                return mWindowSession.performHapticFeedback(effectId, always, fromIme);
+                return mWindowSession.performHapticFeedback(effectId, flags, privFlags);
             }
         } catch (RemoteException e) {
             return false;
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 55f22a6..7871858 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -503,13 +503,13 @@
     }
 
     @Override
-    public boolean performHapticFeedback(int effectId, boolean always, boolean fromIme) {
+    public boolean performHapticFeedback(int effectId, int flags, int privFlags) {
         return false;
     }
 
     @Override
-    public void performHapticFeedbackAsync(int effectId, boolean always, boolean fromIme) {
-        performHapticFeedback(effectId, always, fromIme);
+    public void performHapticFeedbackAsync(int effectId, int flags, int privFlags) {
+        performHapticFeedback(effectId, flags, privFlags);
     }
 
     @Override
diff --git a/core/java/android/view/accessibility/a11ychecker/Android.bp b/core/java/android/view/accessibility/a11ychecker/Android.bp
deleted file mode 100644
index e5a577c..0000000
--- a/core/java/android/view/accessibility/a11ychecker/Android.bp
+++ /dev/null
@@ -1,7 +0,0 @@
-java_library_static {
-    name: "A11yChecker",
-    srcs: [
-        "*.java",
-    ],
-    visibility: ["//visibility:public"],
-}
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index 739cf0e..0d5e37e 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -75,8 +75,8 @@
     /**
      * The core implementation to obtain {@link WindowMetrics}
      *
-     * @param isMaximum {@code true} to obtain {@link WindowManager#getCurrentWindowMetrics()}.
-     *                  {@code false} to obtain {@link WindowManager#getMaximumWindowMetrics()}.
+     * @param isMaximum {@code false} to obtain {@link WindowManager#getCurrentWindowMetrics()}.
+     *                  {@code true} to obtain {@link WindowManager#getMaximumWindowMetrics()}.
      */
     private WindowMetrics getWindowMetricsInternal(boolean isMaximum) {
         final Rect bounds;
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index 82ae76a..ac4c066 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -43,7 +43,7 @@
     void switchToPreviousInputMethod(in AndroidFuture future /* T=Boolean */);
     void switchToNextInputMethod(boolean onlyCurrentIme, in AndroidFuture future /* T=Boolean */);
     void shouldOfferSwitchingToNextInputMethod(in AndroidFuture future /* T=Boolean */);
-    void onImeSwitchButtonClickFromClient(int displayId, int userId);
+    void onImeSwitchButtonClickFromClient(int displayId);
     void notifyUserActionAsync();
     void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible,
             in ImeTracker.Token statsToken);
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index f50f02e..2daf0fd 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -378,16 +378,16 @@
     }
 
     /**
-     * Calls {@link IInputMethodPrivilegedOperations#onImeSwitchButtonClickFromClient(int, int)}
+     * Calls {@link IInputMethodPrivilegedOperations#onImeSwitchButtonClickFromClient(int)}
      */
     @AnyThread
-    public void onImeSwitchButtonClickFromClient(int displayId, int userId) {
+    public void onImeSwitchButtonClickFromClient(int displayId) {
         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
         if (ops == null) {
             return;
         }
         try {
-            ops.onImeSwitchButtonClickFromClient(displayId, userId);
+            ops.onImeSwitchButtonClickFromClient(displayId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 7c62615..c07fd38 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -27,7 +27,6 @@
 #include <android_media_audiopolicy.h>
 #include <android_os_Parcel.h>
 #include <audiomanager/AudioManager.h>
-#include <android-base/properties.h>
 #include <binder/IBinder.h>
 #include <jni.h>
 #include <media/AidlConversion.h>
@@ -42,10 +41,8 @@
 #include <system/audio_policy.h>
 #include <utils/Log.h>
 
-#include <thread>
 #include <optional>
 #include <sstream>
-#include <memory>
 #include <vector>
 
 #include "android_media_AudioAttributes.h"
@@ -264,13 +261,6 @@
     jfieldID mMixerBehavior;
 } gAudioMixerAttributesField;
 
-static struct {
-    jclass clazz;
-    jmethodID run;
-} gRunnableClassInfo;
-
-static JavaVM* gVm;
-
 static Mutex gLock;
 
 enum AudioError {
@@ -3372,55 +3362,6 @@
     return enabled;
 }
 
-class JavaSystemPropertyListener {
-  public:
-    JavaSystemPropertyListener(JNIEnv* env, jobject javaCallback, std::string sysPropName) :
-            mCallback(env->NewGlobalRef(javaCallback)),
-            mCachedProperty(android::base::CachedProperty{std::move(sysPropName)}) {
-        mListenerThread = std::thread([this]() mutable {
-            JNIEnv* threadEnv = GetOrAttachJNIEnvironment(gVm);
-            while (!mCleanupSignal.load()) {
-                using namespace std::chrono_literals;
-                // 1s timeout so this thread can read the cleanup signal to (slowly) be able to
-                // be destroyed.
-                std::string newVal = mCachedProperty.WaitForChange(1000ms) ?: "";
-                if (newVal != "" && mLastVal != newVal) {
-                    threadEnv->CallVoidMethod(mCallback, gRunnableClassInfo.run);
-                    mLastVal = std::move(newVal);
-                }
-            }
-            });
-    }
-
-    ~JavaSystemPropertyListener() {
-        mCleanupSignal.store(true);
-        mListenerThread.join();
-        JNIEnv* env = GetOrAttachJNIEnvironment(gVm);
-        env->DeleteGlobalRef(mCallback);
-    }
-
-  private:
-    jobject mCallback;
-    android::base::CachedProperty mCachedProperty;
-    std::thread mListenerThread;
-    std::atomic<bool> mCleanupSignal{false};
-    std::string mLastVal = "";
-};
-
-std::vector<std::unique_ptr<JavaSystemPropertyListener>> gSystemPropertyListeners;
-std::mutex gSysPropLock{};
-
-static void android_media_AudioSystem_listenForSystemPropertyChange(JNIEnv *env,  jobject thiz,
-        jstring sysProp,
-        jobject javaCallback) {
-    ScopedUtfChars sysPropChars{env, sysProp};
-    auto listener = std::make_unique<JavaSystemPropertyListener>(env, javaCallback,
-            std::string{sysPropChars.c_str()});
-    std::unique_lock _l{gSysPropLock};
-    gSystemPropertyListeners.push_back(std::move(listener));
-}
-
-
 // ----------------------------------------------------------------------------
 
 #define MAKE_AUDIO_SYSTEM_METHOD(x) \
@@ -3593,12 +3534,7 @@
                                 android_media_AudioSystem_clearPreferredMixerAttributes),
          MAKE_AUDIO_SYSTEM_METHOD(supportsBluetoothVariableLatency),
          MAKE_AUDIO_SYSTEM_METHOD(setBluetoothVariableLatencyEnabled),
-         MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled),
-         MAKE_JNI_NATIVE_METHOD("listenForSystemPropertyChange",
-                                "(Ljava/lang/String;Ljava/lang/Runnable;)V",
-                                android_media_AudioSystem_listenForSystemPropertyChange),
-
-        };
+         MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled)};
 
 static const JNINativeMethod gEventHandlerMethods[] =
         {MAKE_JNI_NATIVE_METHOD("native_setup", "(Ljava/lang/Object;)V",
@@ -3880,12 +3816,6 @@
     gAudioMixerAttributesField.mMixerBehavior =
             GetFieldIDOrDie(env, audioMixerAttributesClass, "mMixerBehavior", "I");
 
-    jclass runnableClazz = FindClassOrDie(env, "java/lang/Runnable");
-    gRunnableClassInfo.clazz = MakeGlobalRefOrDie(env, runnableClazz);
-    gRunnableClassInfo.run = GetMethodIDOrDie(env, runnableClazz, "run", "()V");
-
-    LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&gVm) != 0);
-
     AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback);
 
     RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index d11166f..e874163 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -16,18 +16,22 @@
 
 #define LOG_TAG "InputChannel-JNI"
 
-#include "android-base/stringprintf.h"
-#include <nativehelper/JNIHelp.h>
-#include "nativehelper/scoped_utf_chars.h"
+#include "android_view_InputChannel.h"
+
 #include <android_runtime/AndroidRuntime.h>
 #include <binder/Parcel.h>
-#include <utils/Log.h>
+#include <com_android_input_flags.h>
 #include <input/InputTransport.h>
-#include "android_view_InputChannel.h"
+#include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
+
+#include "android-base/stringprintf.h"
 #include "android_os_Parcel.h"
 #include "android_util_Binder.h"
-
 #include "core_jni_helpers.h"
+#include "nativehelper/scoped_utf_chars.h"
+
+namespace input_flags = com::android::input::flags;
 
 namespace android {
 
@@ -69,6 +73,9 @@
 }
 
 void NativeInputChannel::setDisposeCallback(InputChannelObjDisposeCallback callback, void* data) {
+    if (input_flags::remove_input_channel_from_windowstate()) {
+        return;
+    }
     mDisposeCallback = callback;
     mDisposeData = data;
 }
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index d277169..41696df 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -63,7 +63,6 @@
         "-c fa",
     ],
     static_libs: [
-        "A11yChecker",
         "collector-device-lib-platform",
         "frameworks-base-testutils",
         "core-test-rules", // for libcore.dalvik.system.CloseGuardSupport
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 033ac7c..c5b75ff 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import static android.util.SequenceUtils.getInitSeq;
+import static android.view.HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING;
 import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
@@ -494,8 +495,8 @@
                 0, displayInfo, new DisplayAdjustments());
         ViewRootImpl viewRootImpl = new ViewRootImpl(sContext, display);
 
-        boolean result = viewRootImpl.performHapticFeedback(
-                HapticFeedbackConstants.CONTEXT_CLICK, true, false /* fromIme */);
+        boolean result = viewRootImpl.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK,
+                FLAG_IGNORE_GLOBAL_SETTING, 0 /* privFlags */);
 
         assertThat(result).isFalse();
     }
diff --git a/core/tests/coretests/src/android/view/accessibility/a11ychecker/OWNERS b/core/tests/coretests/src/android/view/accessibility/a11ychecker/OWNERS
deleted file mode 100644
index 872a180..0000000
--- a/core/tests/coretests/src/android/view/accessibility/a11ychecker/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Android Accessibility Framework owners
-include /core/java/android/view/accessibility/a11ychecker/OWNERS
-include /services/accessibility/OWNERS
-
-yaraabdullatif@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index 196f89d..df80946 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -107,4 +107,24 @@
      * @param pilferCallback the callback to pilfer pointers.
      */
     void setPilferPointerCallback(Runnable pilferCallback);
+
+    /**
+     * Set a callback to requestTopUi.
+     * @param topUiRequest the callback to requestTopUi.
+     */
+    void setTopUiRequestCallback(TopUiRequest topUiRequest);
+
+    /**
+     * Callback to request SysUi to call
+     * {@link android.app.IActivityManager#setHasTopUi(boolean)}.
+     */
+    interface TopUiRequest {
+
+        /**
+         * Request {@link android.app.IActivityManager#setHasTopUi(boolean)} to be called.
+         * @param requestTopUi  whether topUi should be requested or not
+         * @param tag           tag of the request-source
+         */
+        void requestTopUi(boolean requestTopUi, String tag);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 8467e97..bb239ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -179,6 +179,7 @@
     @BackNavigationInfo.BackTargetType
     private int mPreviousNavigationType;
     private Runnable mPilferPointerCallback;
+    private BackAnimation.TopUiRequest mRequestTopUiCallback;
 
     public BackAnimationController(
             @NonNull ShellInit shellInit,
@@ -357,6 +358,11 @@
                 mPilferPointerCallback = callback;
             });
         }
+
+        @Override
+        public void setTopUiRequestCallback(TopUiRequest topUiRequest) {
+            mShellExecutor.execute(() -> mRequestTopUiCallback = topUiRequest);
+        }
     }
 
     private static class IBackAnimationImpl extends IBackAnimation.Stub
@@ -557,6 +563,7 @@
             if (!mShellBackAnimationRegistry.startGesture(backType)) {
                 mActiveCallback = null;
             }
+            requestTopUi(true, backType);
             tryPilferPointers();
         } else {
             mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback();
@@ -906,6 +913,7 @@
             mPreviousNavigationType = mBackNavigationInfo.getType();
             mBackNavigationInfo.onBackNavigationFinished(triggerBack);
             mBackNavigationInfo = null;
+            requestTopUi(false, mPreviousNavigationType);
         }
     }
 
@@ -969,6 +977,13 @@
         }
     }
 
+    private void requestTopUi(boolean hasTopUi, int backType) {
+        if (mRequestTopUiCallback != null && (backType == BackNavigationInfo.TYPE_CROSS_TASK
+                || backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY)) {
+            mRequestTopUiCallback.requestTopUi(hasTopUi, TAG);
+        }
+    }
+
     /**
      * Validate animation targets.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index 4f04c5c..4e0c82b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -61,8 +61,7 @@
     private val context: Context,
     private val background: BackAnimationBackground,
     private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
-    protected val transaction: SurfaceControl.Transaction,
-    private val choreographer: Choreographer
+    protected val transaction: SurfaceControl.Transaction
 ) : ShellBackAnimation() {
 
     protected val startClosingRect = RectF()
@@ -269,7 +268,9 @@
             .setSpring(postCommitFlingSpring)
         flingAnimation.start()
         // do an animation-frame immediately to prevent idle frame
-        flingAnimation.doAnimationFrame(choreographer.lastFrameTimeNanos / TimeUtils.NANOS_PER_MS)
+        flingAnimation.doAnimationFrame(
+            Choreographer.getInstance().lastFrameTimeNanos / TimeUtils.NANOS_PER_MS
+        )
 
         val valueAnimator =
             ValueAnimator.ofFloat(1f, 0f).setDuration(getPostCommitAnimationDuration())
@@ -362,7 +363,7 @@
     }
 
     protected fun applyTransaction() {
-        transaction.setFrameTimelineVsync(choreographer.vsyncId)
+        transaction.setFrameTimelineVsync(Choreographer.getInstance().vsyncId)
         transaction.apply()
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 103a654..e2b0513 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -52,7 +52,6 @@
 import com.android.internal.protolog.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.animation.Interpolators;
-import com.android.wm.shell.shared.annotations.ShellMainThread;
 
 import javax.inject.Inject;
 
@@ -69,7 +68,6 @@
  * IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back navigation to
  * launcher starts.
  */
-@ShellMainThread
 public class CrossTaskBackAnimation extends ShellBackAnimation {
     private static final int BACKGROUNDCOLOR = 0x43433A;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
index e266e2c..b02f97b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
@@ -19,7 +19,6 @@
 import android.graphics.Rect
 import android.graphics.RectF
 import android.util.MathUtils
-import android.view.Choreographer
 import android.view.SurfaceControl
 import android.view.animation.Animation
 import android.view.animation.Transformation
@@ -31,27 +30,23 @@
 import com.android.internal.protolog.ProtoLog
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.protolog.ShellProtoLogGroup
-import com.android.wm.shell.shared.annotations.ShellMainThread
 import javax.inject.Inject
 import kotlin.math.max
 import kotlin.math.min
 
 /** Class that handles customized predictive cross activity back animations. */
-@ShellMainThread
 class CustomCrossActivityBackAnimation(
     context: Context,
     background: BackAnimationBackground,
     rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
     transaction: SurfaceControl.Transaction,
-    choreographer: Choreographer,
     private val customAnimationLoader: CustomAnimationLoader
 ) :
     CrossActivityBackAnimation(
         context,
         background,
         rootTaskDisplayAreaOrganizer,
-        transaction,
-        choreographer
+        transaction
     ) {
 
     private var enterAnimation: Animation? = null
@@ -70,7 +65,6 @@
         background,
         rootTaskDisplayAreaOrganizer,
         SurfaceControl.Transaction(),
-        Choreographer.getInstance(),
         CustomAnimationLoader(
             TransitionAnimation(context, false /* debug */, "CustomCrossActivityBackAnimation")
         )
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
index 3b5eb36..c747e1e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
@@ -16,18 +16,15 @@
 package com.android.wm.shell.back
 
 import android.content.Context
-import android.view.Choreographer
 import android.view.SurfaceControl
 import android.window.BackEvent
 import com.android.wm.shell.R
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.animation.Interpolators
-import com.android.wm.shell.shared.annotations.ShellMainThread
 import javax.inject.Inject
 import kotlin.math.max
 
 /** Class that defines cross-activity animation. */
-@ShellMainThread
 class DefaultCrossActivityBackAnimation
 @Inject
 constructor(
@@ -39,8 +36,7 @@
         context,
         background,
         rootTaskDisplayAreaOrganizer,
-        SurfaceControl.Transaction(),
-        Choreographer.getInstance()
+        SurfaceControl.Transaction()
     ) {
 
     private val postCommitInterpolator = Interpolators.EMPHASIZED
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index ca05864..247cc42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -88,14 +88,17 @@
     /** Add a [VisibleTasksListener] to be notified when freeform tasks are visible or not. */
     fun addVisibleTasksListener(visibleTasksListener: VisibleTasksListener, executor: Executor) {
         visibleTasksListeners[visibleTasksListener] = executor
-        displayData.keyIterator().forEach { displayId ->
-            val visibleTasksCount = getVisibleTaskCount(displayId)
+        displayData.keyIterator().forEach {
             executor.execute {
-                visibleTasksListener.onTasksVisibilityChanged(displayId, visibleTasksCount)
+                visibleTasksListener.onTasksVisibilityChanged(it, visibleTaskCount(it))
             }
         }
     }
 
+    /** Returns a list of all [DisplayData]. */
+    private fun displayDataList(): Sequence<DisplayData> =
+        displayData.valueIterator().asSequence()
+
     /**
      * Add a Consumer which will inform other classes of changes to exclusion regions for all
      * Desktop tasks.
@@ -208,37 +211,17 @@
         return removed
     }
 
-    /** Check if a task with the given [taskId] was marked as an active task */
-    fun isActiveTask(taskId: Int): Boolean {
-        return displayData.valueIterator().asSequence().any { data ->
-            data.activeTasks.contains(taskId)
-        }
-    }
-
-    /** Check if a task with the given [taskId] was marked as a closing task */
-    fun isClosingTask(taskId: Int): Boolean =
-        displayData.valueIterator().asSequence().any { data -> taskId in data.closingTasks }
-
-    /** Whether a task is visible. */
-    fun isVisibleTask(taskId: Int): Boolean {
-        return displayData.valueIterator().asSequence().any { data ->
-            data.visibleTasks.contains(taskId)
-        }
-    }
-
-    /** Return whether the given Task is minimized. */
-    fun isMinimizedTask(taskId: Int): Boolean {
-        return displayData.valueIterator().asSequence().any { data ->
-            data.minimizedTasks.contains(taskId)
-        }
-    }
+    fun isActiveTask(taskId: Int) = displayDataList().any { taskId in it.activeTasks }
+    fun isClosingTask(taskId: Int) = displayDataList().any { taskId in it.closingTasks }
+    fun isVisibleTask(taskId: Int) = displayDataList().any { taskId in it.visibleTasks }
+    fun isMinimizedTask(taskId: Int) = displayDataList().any { taskId in it.minimizedTasks }
 
     /**
      * Check if a task with the given [taskId] is the only visible, non-closing, not-minimized task
      * on its display
      */
     fun isOnlyVisibleNonClosingTask(taskId: Int): Boolean =
-        displayData.valueIterator().asSequence().any { data ->
+        displayDataList().any { data ->
             data.visibleTasks
                 .subtract(data.closingTasks)
                 .subtract(data.minimizedTasks)
@@ -255,12 +238,6 @@
         ArraySet(displayData[displayId]?.minimizedTasks)
 
     /**
-     * Returns whether Desktop Mode is currently showing any tasks, i.e. whether any Desktop Tasks
-     * are visible.
-     */
-    fun isDesktopModeShowing(displayId: Int): Boolean = getVisibleTaskCount(displayId) > 0
-
-    /**
      * Returns a list of Tasks IDs representing all active non-minimized Tasks on the given display,
      * ordered from front to back.
      */
@@ -305,14 +282,14 @@
             return
         }
 
-        val prevCount = getVisibleTaskCount(displayId)
+        val prevCount = visibleTaskCount(displayId)
         if (visible) {
             displayData.getOrCreate(displayId).visibleTasks.add(taskId)
             unminimizeTask(displayId, taskId)
         } else {
             displayData[displayId]?.visibleTasks?.remove(taskId)
         }
-        val newCount = getVisibleTaskCount(displayId)
+        val newCount = visibleTaskCount(displayId)
 
         // Check if count changed
         if (prevCount != newCount) {
@@ -340,7 +317,7 @@
     }
 
     /** Get number of tasks that are marked as visible on given [displayId] */
-    fun getVisibleTaskCount(displayId: Int): Int {
+    fun visibleTaskCount(displayId: Int): Int {
         ProtoLog.d(
             WM_SHELL_DESKTOP_MODE,
             "DesktopTaskRepo: visibleTaskCount= %d",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index d46b2d6..9a1a8a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -246,10 +246,12 @@
         }
     }
 
-    /** Get number of tasks that are marked as visible */
-    fun getVisibleTaskCount(displayId: Int): Int {
-        return desktopModeTaskRepository.getVisibleTaskCount(displayId)
-    }
+    /** Gets number of visible tasks in [displayId]. */
+    fun visibleTaskCount(displayId: Int): Int =
+        desktopModeTaskRepository.visibleTaskCount(displayId)
+
+    /** Returns true if any tasks are visible in Desktop Mode. */
+    fun isDesktopModeShowing(displayId: Int): Boolean = visibleTaskCount(displayId) > 0
 
     /** Enter desktop by using the focused task in given `displayId` */
     fun moveFocusedTaskToDesktop(displayId: Int, transitionSource: DesktopModeTransitionSource) {
@@ -981,7 +983,7 @@
             ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: skip keyguard is locked")
             return null
         }
-        if (!desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
+        if (!isDesktopModeShowing(task.displayId)) {
             ProtoLog.d(
                 WM_SHELL_DESKTOP_MODE,
                 "DesktopTasksController: bring desktop tasks to front on transition" +
@@ -1012,7 +1014,7 @@
         transition: IBinder
     ): WindowContainerTransaction? {
         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch")
-        if (desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
+        if (isDesktopModeShowing(task.displayId)) {
             ProtoLog.d(
                 WM_SHELL_DESKTOP_MODE,
                 "DesktopTasksController: switch fullscreen task to freeform on transition" +
@@ -1396,8 +1398,7 @@
         onFinishCallback: Consumer<Boolean>
     ): Boolean {
         // TODO(b/320797628): Pass through which display we are dropping onto
-        val activeTasks = desktopModeTaskRepository.getActiveTasks(DEFAULT_DISPLAY)
-        if (!activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
+        if (!isDesktopModeShowing(DEFAULT_DISPLAY)) {
             // Not currently in desktop mode, ignore the drop
             return false
         }
@@ -1556,8 +1557,8 @@
             val result = IntArray(1)
             executeRemoteCallWithTaskPermission(
                 controller,
-                "getVisibleTaskCount",
-                { controller -> result[0] = controller.getVisibleTaskCount(displayId) },
+                "visibleTaskCount",
+                { controller -> result[0] = controller.visibleTaskCount(displayId) },
                 true /* blocking */
             )
             return result[0]
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index ff40d4c..8d63ff2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1980,12 +1980,6 @@
             }
             clearContentOverlay();
         }
-        if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
-            // Avoid double removal, which is fatal.
-            ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: trying to remove overlay (%s) while in UNDEFINED state", TAG, surface);
-            return;
-        }
         if (surface == null || !surface.isValid()) {
             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: trying to remove invalid content overlay (%s)", TAG, surface);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 4104234..9bcd9b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -3573,7 +3573,8 @@
         pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible);
         pw.println(innerPrefix + "isSplitActive=" + isSplitActive());
         pw.println(innerPrefix + "isSplitVisible=" + isSplitScreenVisible());
-        pw.println(innerPrefix + "isLeftRightSplit=" + mSplitLayout.isLeftRightSplit());
+        pw.println(innerPrefix + "isLeftRightSplit="
+                + (mSplitLayout != null ? mSplitLayout.isLeftRightSplit() : "null"));
         pw.println(innerPrefix + "MainStage");
         pw.println(childPrefix + "stagePosition=" + splitPositionToString(getMainStagePosition()));
         pw.println(childPrefix + "isActive=" + mMainStage.isActive());
@@ -3585,7 +3586,9 @@
         mSideStage.dump(pw, childPrefix);
         pw.println(innerPrefix + "SideStageListener");
         mSideStageListener.dump(pw, childPrefix);
-        mSplitLayout.dump(pw, childPrefix);
+        if (mSplitLayout != null) {
+            mSplitLayout.dump(pw, childPrefix);
+        }
         if (!mPausingTasks.isEmpty()) {
             pw.println(childPrefix + "mPausingTasks=" + mPausingTasks);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 4f4b809..766a6b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -353,7 +353,7 @@
                 return this::setRecentsTransitionDuringKeyguard;
             } else if (mDesktopTasksController != null
                     // Check on the default display. Recents/gesture nav is only available there
-                    && mDesktopTasksController.getVisibleTaskCount(DEFAULT_DISPLAY) > 0) {
+                    && mDesktopTasksController.visibleTaskCount(DEFAULT_DISPLAY) > 0) {
                 return this::setRecentsTransitionDuringDesktop;
             }
         }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
index 8bf0111..080ad90 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
@@ -25,7 +25,6 @@
 import android.os.RemoteException
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
-import android.view.Choreographer
 import android.view.RemoteAnimationTarget
 import android.view.SurfaceControl
 import android.view.SurfaceControl.Transaction
@@ -37,8 +36,6 @@
 import com.android.internal.policy.TransitionAnimation
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTestCase
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
 import junit.framework.TestCase.assertEquals
 import org.junit.Assert
 import org.junit.Before
@@ -50,12 +47,13 @@
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
-import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.kotlin.spy
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
 
 @SmallTest
 @TestableLooper.RunWithLooper
@@ -82,7 +80,6 @@
                 backAnimationBackground,
                 rootTaskDisplayAreaOrganizer,
                 transaction,
-                mock(Choreographer::class.java),
                 customAnimationLoader
             )
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 6612aee..18b08bf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -337,65 +337,65 @@
     }
 
     @Test
-    fun getVisibleTaskCount() {
+    fun visibleTaskCount_defaultDisplay_returnsCorrectCount() {
         // No tasks, count is 0
-        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+        assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
 
         // New task increments count to 1
         repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
-        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+        assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
 
         // Visibility update to same task does not increase count
         repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
-        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+        assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
 
         // Second task visible increments count
         repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
-        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
+        assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
 
         // Hiding a task decrements count
         repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
-        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+        assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
 
         // Hiding all tasks leaves count at 0
         repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = false)
-        assertThat(repo.getVisibleTaskCount(displayId = 9)).isEqualTo(0)
+        assertThat(repo.visibleTaskCount(displayId = 9)).isEqualTo(0)
 
         // Hiding a not existing task, count remains at 0
         repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 999, visible = false)
-        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+        assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
     }
 
     @Test
-    fun getVisibleTaskCount_multipleDisplays() {
-        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
-        assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
+    fun visibleTaskCount_multipleDisplays_returnsCorrectCount() {
+        assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+        assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
 
         // New task on default display increments count for that display only
         repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
-        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
-        assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
+        assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+        assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
 
         // New task on secondary display, increments count for that display only
         repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 2, visible = true)
-        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
-        assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
+        assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+        assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
 
         // Marking task visible on another display, updates counts for both displays
         repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true)
-        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
-        assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
+        assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+        assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
 
         // Marking task that is on secondary display, hidden on default display, does not affect
         // secondary display
         repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
-        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
-        assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
+        assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+        assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
 
         // Hiding a task on that display, decrements count
         repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = false)
-        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
-        assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
+        assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+        assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
     }
 
     @Test
@@ -494,28 +494,6 @@
     }
 
     @Test
-    fun isDesktopModeShowing_noActiveTasks_returnsFalse() {
-        assertThat(repo.isDesktopModeShowing(displayId = 0)).isFalse()
-    }
-
-    @Test
-    fun isDesktopModeShowing_noTasksVisible_returnsFalse() {
-        repo.addActiveTask(displayId = 0, taskId = 1)
-        repo.addActiveTask(displayId = 0, taskId = 2)
-
-        assertThat(repo.isDesktopModeShowing(displayId = 0)).isFalse()
-    }
-
-    @Test
-    fun isDesktopModeShowing_tasksActiveAndVisible_returnsTrue() {
-        repo.addActiveTask(displayId = 0, taskId = 1)
-        repo.addActiveTask(displayId = 0, taskId = 2)
-        repo.updateVisibleFreeformTasks(displayId = 0, taskId = 1, visible = true)
-
-        assertThat(repo.isDesktopModeShowing(displayId = 0)).isTrue()
-    }
-
-    @Test
     fun getActiveNonMinimizedTasksOrderedFrontToBack_returnsFreeformTasksInCorrectOrder() {
         repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1)
         repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index c56671a..da88686 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -311,6 +311,31 @@
   }
 
   @Test
+  fun isDesktopModeShowing_noTasks_returnsFalse() {
+    assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse()
+  }
+
+  @Test
+  fun isDesktopModeShowing_noTasksVisible_returnsFalse() {
+    val task1 = setUpFreeformTask()
+    val task2 = setUpFreeformTask()
+    markTaskHidden(task1)
+    markTaskHidden(task2)
+
+    assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse()
+  }
+
+  @Test
+  fun isDesktopModeShowing_tasksActiveAndVisible_returnsTrue() {
+    val task1 = setUpFreeformTask()
+    val task2 = setUpFreeformTask()
+    markTaskVisible(task1)
+    markTaskHidden(task2)
+
+    assertThat(controller.isDesktopModeShowing(displayId = 0)).isTrue()
+  }
+
+  @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
   fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() {
     val homeTask = setUpHomeTask(SECOND_DISPLAY)
@@ -526,32 +551,32 @@
   }
 
   @Test
-  fun getVisibleTaskCount_noTasks_returnsZero() {
-    assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+  fun visibleTaskCount_noTasks_returnsZero() {
+    assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
   }
 
   @Test
-  fun getVisibleTaskCount_twoTasks_bothVisible_returnsTwo() {
+  fun visibleTaskCount_twoTasks_bothVisible_returnsTwo() {
     setUpHomeTask()
     setUpFreeformTask().also(::markTaskVisible)
     setUpFreeformTask().also(::markTaskVisible)
-    assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
+    assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
   }
 
   @Test
-  fun getVisibleTaskCount_twoTasks_oneVisible_returnsOne() {
+  fun visibleTaskCount_twoTasks_oneVisible_returnsOne() {
     setUpHomeTask()
     setUpFreeformTask().also(::markTaskVisible)
     setUpFreeformTask().also(::markTaskHidden)
-    assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+    assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
   }
 
   @Test
-  fun getVisibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() {
+  fun visibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() {
     setUpHomeTask()
     setUpFreeformTask(DEFAULT_DISPLAY).also(::markTaskVisible)
     setUpFreeformTask(SECOND_DISPLAY).also(::markTaskVisible)
-    assertThat(controller.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
+    assertThat(controller.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
   }
 
   @Test
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 52b5ff7..d148afd 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -2649,11 +2649,4 @@
      * @hide
      */
     public static native boolean isBluetoothVariableLatencyEnabled();
-
-    /**
-     * Register a native listener for system property sysprop
-     * @param callback the listener which fires when the property changes
-     * @hide
-     */
-    public static native void listenForSystemPropertyChange(String sysprop, Runnable callback);
 }
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index d42b256..d20b7f0 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -762,14 +762,14 @@
 
     void unregisterLoudnessCodecUpdatesDispatcher(in ILoudnessCodecUpdatesDispatcher dispatcher);
 
-    oneway void startLoudnessCodecUpdates(int sessionId);
+    void startLoudnessCodecUpdates(int sessionId);
 
-    oneway void stopLoudnessCodecUpdates(int sessionId);
+    void stopLoudnessCodecUpdates(int sessionId);
 
-    oneway void addLoudnessCodecInfo(int sessionId, int mediaCodecHash,
+    void addLoudnessCodecInfo(int sessionId, int mediaCodecHash,
             in LoudnessCodecInfo codecInfo);
 
-    oneway void removeLoudnessCodecInfo(int sessionId, in LoudnessCodecInfo codecInfo);
+    void removeLoudnessCodecInfo(int sessionId, in LoudnessCodecInfo codecInfo);
 
     PersistableBundle getLoudnessParams(in LoudnessCodecInfo codecInfo);
 
diff --git a/nfc/java/android/nfc/AvailableNfcAntenna.java b/nfc/java/android/nfc/AvailableNfcAntenna.java
index 6e6512a..e76aeb0 100644
--- a/nfc/java/android/nfc/AvailableNfcAntenna.java
+++ b/nfc/java/android/nfc/AvailableNfcAntenna.java
@@ -28,13 +28,13 @@
 public final class AvailableNfcAntenna implements Parcelable {
     /**
      * Location of the antenna on the Y axis in millimeters.
-     * 0 is the bottom-left when the user is facing the screen
+     * 0 is the top-left when the user is facing the screen
      * and the device orientation is Portrait.
      */
     private final int mLocationX;
     /**
      * Location of the antenna on the Y axis in millimeters.
-     * 0 is the bottom-left when the user is facing the screen
+     * 0 is the top-left when the user is facing the screen
      * and the device orientation is Portrait.
      */
     private final int mLocationY;
@@ -46,7 +46,7 @@
 
     /**
      * Location of the antenna on the X axis in millimeters.
-     * 0 is the bottom-left when the user is facing the screen
+     * 0 is the top-left when the user is facing the screen
      * and the device orientation is Portrait.
      */
     public int getLocationX() {
@@ -55,7 +55,7 @@
 
     /**
      * Location of the antenna on the Y axis in millimeters.
-     * 0 is the bottom-left when the user is facing the screen
+     * 0 is the top-left when the user is facing the screen
      * and the device orientation is Portrait.
      */
     public int getLocationY() {
diff --git a/nfc/java/android/nfc/NfcAntennaInfo.java b/nfc/java/android/nfc/NfcAntennaInfo.java
index b002ca2..c57b2e0 100644
--- a/nfc/java/android/nfc/NfcAntennaInfo.java
+++ b/nfc/java/android/nfc/NfcAntennaInfo.java
@@ -64,9 +64,9 @@
 
     /**
      * Whether the device is foldable. When the device is foldable,
-     * the 0, 0 is considered to be bottom-left when the device is unfolded and
+     * the 0, 0 is considered to be top-left when the device is unfolded and
      * the screens are facing the user. For non-foldable devices 0, 0
-     * is bottom-left when the user is facing the screen.
+     * is top-left when the user is facing the screen.
      */
     public boolean isDeviceFoldable() {
         return mDeviceFoldable;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManagerExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManagerExt.kt
new file mode 100644
index 0000000..2eaa804
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManagerExt.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.settingslib.bluetooth
+
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.launch
+
+/** [Flow] for [BluetoothCallback] device profile connection state change events */
+val BluetoothEventManager.onProfileConnectionStateChanged: Flow<ProfileConnectionState>
+    get() = callbackFlow {
+        val callback =
+            object : BluetoothCallback {
+                override fun onProfileConnectionStateChanged(
+                    cachedDevice: CachedBluetoothDevice,
+                    @BluetoothCallback.ConnectionState state: Int,
+                    bluetoothProfile: Int
+                ) {
+                    launch { send(ProfileConnectionState(cachedDevice, state, bluetoothProfile)) }
+                }
+            }
+        registerCallback(callback)
+        awaitClose { unregisterCallback(callback) }
+    }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt
new file mode 100644
index 0000000..91a99ae
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.settingslib.bluetooth
+
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothLeBroadcastAssistant
+import android.bluetooth.BluetoothLeBroadcastMetadata
+import android.bluetooth.BluetoothLeBroadcastReceiveState
+import com.android.internal.util.ConcurrentUtils
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.launch
+
+/** [Flow] for [BluetoothLeBroadcastAssistant.Callback] source connected/removed events */
+val LocalBluetoothLeBroadcastAssistant.onSourceConnectedOrRemoved: Flow<Unit>
+    get() = callbackFlow {
+        val callback =
+            object : BluetoothLeBroadcastAssistant.Callback {
+                override fun onReceiveStateChanged(
+                    sink: BluetoothDevice,
+                    sourceId: Int,
+                    state: BluetoothLeBroadcastReceiveState
+                ) {
+                    if (BluetoothUtils.isConnected(state)) {
+                        launch { send(Unit) }
+                    }
+                }
+
+                override fun onSourceRemoved(sink: BluetoothDevice, sourceId: Int, reason: Int) {
+                    launch { send(Unit) }
+                }
+
+                override fun onSearchStarted(reason: Int) {}
+
+                override fun onSearchStartFailed(reason: Int) {}
+
+                override fun onSearchStopped(reason: Int) {}
+
+                override fun onSearchStopFailed(reason: Int) {}
+
+                override fun onSourceFound(source: BluetoothLeBroadcastMetadata) {}
+
+                override fun onSourceAdded(sink: BluetoothDevice, sourceId: Int, reason: Int) {}
+
+                override fun onSourceAddFailed(
+                    sink: BluetoothDevice,
+                    source: BluetoothLeBroadcastMetadata,
+                    reason: Int
+                ) {}
+
+                override fun onSourceModified(sink: BluetoothDevice, sourceId: Int, reason: Int) {}
+
+                override fun onSourceModifyFailed(
+                    sink: BluetoothDevice,
+                    sourceId: Int,
+                    reason: Int
+                ) {}
+
+                override fun onSourceRemoveFailed(
+                    sink: BluetoothDevice,
+                    sourceId: Int,
+                    reason: Int
+                ) {}
+            }
+        registerServiceCallBack(
+            ConcurrentUtils.DIRECT_EXECUTOR,
+            callback,
+        )
+        awaitClose { unregisterServiceCallBack(callback) }
+    }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/ProfileConnectionState.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/ProfileConnectionState.kt
new file mode 100644
index 0000000..45aaa66
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/ProfileConnectionState.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.settingslib.bluetooth
+
+data class ProfileConnectionState(
+    val cachedDevice: CachedBluetoothDevice,
+    @BluetoothCallback.ConnectionState val state: Int,
+    val bluetoothProfile: Int,
+)
\ No newline at end of file
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 9dbf23e..eb33a7a 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
@@ -16,33 +16,84 @@
 
 package com.android.settingslib.volume.data.repository
 
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothCsipSetCoordinator
+import android.bluetooth.BluetoothDevice
 import android.bluetooth.BluetoothLeBroadcast
 import android.bluetooth.BluetoothLeBroadcastMetadata
+import android.bluetooth.BluetoothProfile
+import android.bluetooth.BluetoothVolumeControl
+import android.content.ContentResolver
+import android.content.Context
+import android.database.ContentObserver
+import android.provider.Settings
+import androidx.annotation.IntRange
 import com.android.internal.util.ConcurrentUtils
+import com.android.settingslib.bluetooth.BluetoothUtils
 import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.bluetooth.onProfileConnectionStateChanged
+import com.android.settingslib.bluetooth.onSourceConnectedOrRemoved
 import com.android.settingslib.flags.Flags
+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 kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.runningFold
+import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+typealias GroupIdToVolumes = Map<Int, Int>
 
 /** Provides audio sharing functionality. */
 interface AudioSharingRepository {
     /** Whether the device is in audio sharing. */
     val inAudioSharing: Flow<Boolean>
+
+    /** The secondary headset groupId in audio sharing. */
+    val secondaryGroupId: StateFlow<Int>
+
+    /** The headset groupId to volume map during audio sharing. */
+    val volumeMap: StateFlow<GroupIdToVolumes>
+
+    /** Set the volume of secondary headset during audio sharing. */
+    suspend fun setSecondaryVolume(
+        @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
+        volume: Int
+    )
+
+    companion object {
+        const val AUDIO_SHARING_VOLUME_MIN = 0
+        const val AUDIO_SHARING_VOLUME_MAX = 255
+    }
 }
 
+@OptIn(ExperimentalCoroutinesApi::class)
 class AudioSharingRepositoryImpl(
-    private val localBluetoothManager: LocalBluetoothManager?,
-    backgroundCoroutineContext: CoroutineContext,
+    private val context: Context,
+    private val contentResolver: ContentResolver,
+    private val btManager: LocalBluetoothManager?,
+    private val coroutineScope: CoroutineScope,
+    private val backgroundCoroutineContext: CoroutineContext,
 ) : AudioSharingRepository {
     override val inAudioSharing: Flow<Boolean> =
         if (Flags.enableLeAudioSharing()) {
-            localBluetoothManager?.profileManager?.leAudioBroadcastProfile?.let { leBroadcast ->
+            btManager?.profileManager?.leAudioBroadcastProfile?.let { leBroadcast ->
                 callbackFlow {
                         val listener =
                             object : BluetoothLeBroadcast.Callback {
@@ -92,9 +143,117 @@
             flowOf(false)
         }
 
+    private val primaryChange: Flow<Unit> = callbackFlow {
+        val callback =
+            object : ContentObserver(null) {
+                override fun onChange(selfChange: Boolean) {
+                    launch { send(Unit) }
+                }
+            }
+        contentResolver.registerContentObserver(
+            Settings.Secure.getUriFor(BluetoothUtils.getPrimaryGroupIdUriForBroadcast()),
+            false,
+            callback)
+        awaitClose { contentResolver.unregisterContentObserver(callback) }
+    }
+
+    override val secondaryGroupId: StateFlow<Int> =
+        if (Flags.volumeDialogAudioSharingFix()) {
+                merge(
+                        btManager
+                            ?.profileManager
+                            ?.leAudioBroadcastAssistantProfile
+                            ?.onSourceConnectedOrRemoved
+                            ?.map { getSecondaryGroupId() } ?: emptyFlow(),
+                        btManager
+                            ?.eventManager
+                            ?.onProfileConnectionStateChanged
+                            ?.filter { profileConnection ->
+                                profileConnection.state == BluetoothAdapter.STATE_DISCONNECTED &&
+                                    profileConnection.bluetoothProfile ==
+                                        BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
+                            }
+                            ?.map { getSecondaryGroupId() } ?: emptyFlow(),
+                        primaryChange.map { getSecondaryGroupId() })
+                    .onStart { emit(getSecondaryGroupId()) }
+                    .distinctUntilChanged()
+                    .flowOn(backgroundCoroutineContext)
+            } else {
+                emptyFlow()
+            }
+            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), getSecondaryGroupId())
+
+    override val volumeMap: StateFlow<GroupIdToVolumes> =
+        if (Flags.volumeDialogAudioSharingFix()) {
+            btManager?.profileManager?.volumeControlProfile?.let { volumeControl ->
+                inAudioSharing.flatMapLatest { isSharing ->
+                    if (isSharing) {
+                        callbackFlow {
+                                val callback =
+                                    object : BluetoothVolumeControl.Callback {
+                                        override fun onDeviceVolumeChanged(
+                                            device: BluetoothDevice,
+                                            @IntRange(
+                                                from = AUDIO_SHARING_VOLUME_MIN.toLong(),
+                                                to = AUDIO_SHARING_VOLUME_MAX.toLong())
+                                            volume: Int
+                                        ) {
+                                            launch { send(Pair(device, volume)) }
+                                        }
+                                    }
+                                // Once registered, we will receive the initial volume of all
+                                // connected BT devices on VolumeControlProfile via callbacks
+                                volumeControl.registerCallback(
+                                    ConcurrentUtils.DIRECT_EXECUTOR, callback)
+                                awaitClose { volumeControl.unregisterCallback(callback) }
+                            }
+                            .runningFold(emptyMap<Int, Int>()) { acc, value ->
+                                val groupId =
+                                    BluetoothUtils.getGroupId(
+                                        btManager.cachedDeviceManager?.findDevice(value.first))
+                                if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+                                    acc + Pair(groupId, value.second)
+                                } else {
+                                    acc
+                                }
+                            }
+                            .distinctUntilChanged()
+                            .flowOn(backgroundCoroutineContext)
+                    } else {
+                        emptyFlow()
+                    }
+                }
+            } ?: emptyFlow()
+        } else {
+            emptyFlow()
+        }
+        .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyMap())
+
+    override suspend fun setSecondaryVolume(
+        @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
+        volume: Int
+    ) {
+        withContext(backgroundCoroutineContext) {
+            if (Flags.volumeDialogAudioSharingFix()) {
+                btManager?.profileManager?.volumeControlProfile?.let {
+                    // Find secondary headset and set volume.
+                    val cachedDevice =
+                        BluetoothUtils.getSecondaryDeviceForBroadcast(context, btManager)
+                    if (cachedDevice != null) {
+                        it.setDeviceVolume(cachedDevice.device, volume, /* isGroupOp= */ true)
+                    }
+                }
+            }
+        }
+    }
+
     private fun isBroadcasting(): Boolean {
         return Flags.enableLeAudioSharing() &&
-            (localBluetoothManager?.profileManager?.leAudioBroadcastProfile?.isEnabled(null)
-                ?: false)
+            (btManager?.profileManager?.leAudioBroadcastProfile?.isEnabled(null) ?: false)
+    }
+
+    private fun getSecondaryGroupId(): Int {
+        return BluetoothUtils.getGroupId(
+            BluetoothUtils.getSecondaryDeviceForBroadcast(context, btManager))
     }
 }
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 1c80ef4..000664d 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
@@ -16,15 +16,33 @@
 
 package com.android.settingslib.volume.data.repository
 
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
 import android.bluetooth.BluetoothLeBroadcast
+import android.bluetooth.BluetoothLeBroadcastAssistant
+import android.bluetooth.BluetoothLeBroadcastReceiveState
+import android.bluetooth.BluetoothProfile
+import android.bluetooth.BluetoothVolumeControl
+import android.content.ContentResolver
+import android.content.Context
+import android.database.ContentObserver
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
+import android.provider.Settings
+import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.BluetoothCallback
+import com.android.settingslib.bluetooth.BluetoothEventManager
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
+import com.android.settingslib.bluetooth.VolumeControlProfile
 import com.android.settingslib.flags.Flags
 import com.google.common.truth.Truth
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -39,6 +57,9 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.never
@@ -52,27 +73,76 @@
 @RunWith(AndroidJUnit4::class)
 class AudioSharingRepositoryTest {
     @get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
+
     @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
 
-    @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
-    @Mock private lateinit var localBluetoothProfileManager: LocalBluetoothProfileManager
-    @Mock private lateinit var localBluetoothLeBroadcast: LocalBluetoothLeBroadcast
+    @Mock private lateinit var btManager: LocalBluetoothManager
+
+    @Mock private lateinit var profileManager: LocalBluetoothProfileManager
+
+    @Mock private lateinit var broadcast: LocalBluetoothLeBroadcast
+
+    @Mock private lateinit var assistant: LocalBluetoothLeBroadcastAssistant
+
+    @Mock private lateinit var volumeControl: VolumeControlProfile
+
+    @Mock private lateinit var eventManager: BluetoothEventManager
+
+    @Mock private lateinit var deviceManager: CachedBluetoothDeviceManager
+
+    @Mock private lateinit var device1: BluetoothDevice
+
+    @Mock private lateinit var device2: BluetoothDevice
+
+    @Mock private lateinit var cachedDevice1: CachedBluetoothDevice
+
+    @Mock private lateinit var cachedDevice2: CachedBluetoothDevice
+
+    @Mock private lateinit var receiveState: BluetoothLeBroadcastReceiveState
+
+    @Mock private lateinit var contentResolver: ContentResolver
 
     @Captor
-    private lateinit var leBroadcastCallbackCaptor: ArgumentCaptor<BluetoothLeBroadcast.Callback>
-    private val testScope = TestScope()
+    private lateinit var broadcastCallbackCaptor: ArgumentCaptor<BluetoothLeBroadcast.Callback>
 
+    @Captor
+    private lateinit var assistantCallbackCaptor:
+        ArgumentCaptor<BluetoothLeBroadcastAssistant.Callback>
+
+    @Captor private lateinit var btCallbackCaptor: ArgumentCaptor<BluetoothCallback>
+
+    @Captor private lateinit var contentObserverCaptor: ArgumentCaptor<ContentObserver>
+
+    @Captor
+    private lateinit var volumeCallbackCaptor: ArgumentCaptor<BluetoothVolumeControl.Callback>
+
+    private val testScope = TestScope()
+    private val context: Context = ApplicationProvider.getApplicationContext()
     private lateinit var underTest: AudioSharingRepository
 
     @Before
     fun setup() {
-        `when`(localBluetoothManager.profileManager).thenReturn(localBluetoothProfileManager)
-        `when`(localBluetoothProfileManager.leAudioBroadcastProfile)
-            .thenReturn(localBluetoothLeBroadcast)
-        `when`(localBluetoothLeBroadcast.isEnabled(null)).thenReturn(true)
+        `when`(btManager.profileManager).thenReturn(profileManager)
+        `when`(profileManager.leAudioBroadcastProfile).thenReturn(broadcast)
+        `when`(profileManager.leAudioBroadcastAssistantProfile).thenReturn(assistant)
+        `when`(profileManager.volumeControlProfile).thenReturn(volumeControl)
+        `when`(btManager.eventManager).thenReturn(eventManager)
+        `when`(btManager.cachedDeviceManager).thenReturn(deviceManager)
+        `when`(broadcast.isEnabled(null)).thenReturn(true)
+        `when`(cachedDevice1.groupId).thenReturn(TEST_GROUP_ID1)
+        `when`(cachedDevice1.device).thenReturn(device1)
+        `when`(deviceManager.findDevice(device1)).thenReturn(cachedDevice1)
+        `when`(cachedDevice2.groupId).thenReturn(TEST_GROUP_ID2)
+        `when`(cachedDevice2.device).thenReturn(device2)
+        `when`(deviceManager.findDevice(device2)).thenReturn(cachedDevice2)
+        `when`(receiveState.bisSyncState).thenReturn(arrayListOf(TEST_RECEIVE_STATE_CONTENT))
+        `when`(assistant.getAllSources(any())).thenReturn(listOf(receiveState))
         underTest =
             AudioSharingRepositoryImpl(
-                localBluetoothManager,
+                context,
+                contentResolver,
+                btManager,
+                testScope.backgroundScope,
                 testScope.testScheduler,
             )
     }
@@ -84,9 +154,9 @@
             val states = mutableListOf<Boolean?>()
             underTest.inAudioSharing.onEach { states.add(it) }.launchIn(backgroundScope)
             runCurrent()
-            triggerAudioSharingStateChange(false)
+            triggerAudioSharingStateChange(TriggerType.BROADCAST_STOP, broadcastStopped)
             runCurrent()
-            triggerAudioSharingStateChange(true)
+            triggerAudioSharingStateChange(TriggerType.BROADCAST_START, broadcastStarted)
             runCurrent()
 
             Truth.assertThat(states).containsExactly(true, false, true)
@@ -102,19 +172,229 @@
             runCurrent()
 
             Truth.assertThat(states).containsExactly(false)
-            verify(localBluetoothLeBroadcast, never()).registerServiceCallBack(any(), any())
-            verify(localBluetoothLeBroadcast, never()).isEnabled(any())
+            verify(broadcast, never()).registerServiceCallBack(any(), any())
+            verify(broadcast, never()).isEnabled(any())
         }
     }
 
-    private fun triggerAudioSharingStateChange(inAudioSharing: Boolean) {
-        verify(localBluetoothLeBroadcast)
-            .registerServiceCallBack(any(), leBroadcastCallbackCaptor.capture())
-        `when`(localBluetoothLeBroadcast.isEnabled(null)).thenReturn(inAudioSharing)
-        if (inAudioSharing) {
-            leBroadcastCallbackCaptor.value.onBroadcastStarted(0, 0)
-        } else {
-            leBroadcastCallbackCaptor.value.onBroadcastStopped(0, 0)
+    @Test
+    @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+    fun secondaryGroupIdChange_emitValues() {
+        testScope.runTest {
+            val groupIds = mutableListOf<Int?>()
+            underTest.secondaryGroupId.onEach { groupIds.add(it) }.launchIn(backgroundScope)
+            runCurrent()
+            triggerSourceAdded()
+            runCurrent()
+            triggerContentObserverChange()
+            runCurrent()
+            triggerSourceRemoved()
+            runCurrent()
+            triggerSourceAdded()
+            runCurrent()
+            triggerProfileConnectionChange(
+                BluetoothAdapter.STATE_CONNECTING, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
+            runCurrent()
+            triggerProfileConnectionChange(
+                BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO)
+            runCurrent()
+            triggerProfileConnectionChange(
+                BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
+            runCurrent()
+
+            Truth.assertThat(groupIds)
+                .containsExactly(
+                    TEST_GROUP_ID_INVALID,
+                    TEST_GROUP_ID2,
+                    TEST_GROUP_ID1,
+                    TEST_GROUP_ID_INVALID,
+                    TEST_GROUP_ID2,
+                    TEST_GROUP_ID_INVALID)
         }
     }
+
+    @Test
+    @DisableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+    fun secondaryGroupIdChange_audioSharingFlagOff_returnFalse() {
+        testScope.runTest {
+            val groupIds = mutableListOf<Int?>()
+            underTest.secondaryGroupId.onEach { groupIds.add(it) }.launchIn(backgroundScope)
+            runCurrent()
+
+            Truth.assertThat(groupIds).containsExactly(TEST_GROUP_ID_INVALID)
+            verify(assistant, never()).registerServiceCallBack(any(), any())
+            verify(eventManager, never()).registerCallback(any())
+        }
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+    fun volumeMapChange_emitValues() {
+        testScope.runTest {
+            val volumeMaps = mutableListOf<GroupIdToVolumes?>()
+            underTest.volumeMap.onEach { volumeMaps.add(it) }.launchIn(backgroundScope)
+            runCurrent()
+            triggerVolumeMapChange(Pair(device1, TEST_VOLUME1))
+            runCurrent()
+            triggerVolumeMapChange(Pair(device1, TEST_VOLUME2))
+            runCurrent()
+            triggerAudioSharingStateChange(TriggerType.BROADCAST_STOP, broadcastStopped)
+            runCurrent()
+            verify(volumeControl).unregisterCallback(any())
+            runCurrent()
+
+            Truth.assertThat(volumeMaps)
+                .containsExactly(
+                    emptyMap<Int, Int>(),
+                    mapOf(TEST_GROUP_ID1 to TEST_VOLUME1),
+                    mapOf(TEST_GROUP_ID1 to TEST_VOLUME2))
+        }
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+    fun volumeMapChange_audioSharingFlagOff_returnFalse() {
+        testScope.runTest {
+            val volumeMaps = mutableListOf<GroupIdToVolumes?>()
+            underTest.volumeMap.onEach { volumeMaps.add(it) }.launchIn(backgroundScope)
+            runCurrent()
+
+            Truth.assertThat(volumeMaps).isEmpty()
+            verify(broadcast, never()).registerServiceCallBack(any(), any())
+            verify(volumeControl, never()).registerCallback(any(), any())
+        }
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+    fun setSecondaryVolume_setValue() {
+        testScope.runTest {
+            Settings.Secure.putInt(
+                context.contentResolver,
+                BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+                TEST_GROUP_ID2)
+            `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
+            underTest.setSecondaryVolume(TEST_VOLUME1)
+
+            runCurrent()
+            verify(volumeControl).setDeviceVolume(device1, TEST_VOLUME1, true)
+        }
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+    fun setSecondaryVolume_audioSharingFlagOff_doNothing() {
+        testScope.runTest {
+            Settings.Secure.putInt(
+                context.contentResolver,
+                BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+                TEST_GROUP_ID2)
+            `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
+            underTest.setSecondaryVolume(TEST_VOLUME1)
+
+            runCurrent()
+            verify(volumeControl, never()).setDeviceVolume(any(), anyInt(), anyBoolean())
+        }
+    }
+
+    private fun triggerAudioSharingStateChange(
+        type: TriggerType,
+        broadcastAction: BluetoothLeBroadcast.Callback.() -> Unit
+    ) {
+        verify(broadcast).registerServiceCallBack(any(), broadcastCallbackCaptor.capture())
+        when (type) {
+            TriggerType.BROADCAST_START -> {
+                `when`(broadcast.isEnabled(null)).thenReturn(true)
+                broadcastCallbackCaptor.value.broadcastAction()
+            }
+            TriggerType.BROADCAST_STOP -> {
+                `when`(broadcast.isEnabled(null)).thenReturn(false)
+                broadcastCallbackCaptor.value.broadcastAction()
+            }
+        }
+    }
+
+    private fun triggerSourceAdded() {
+        verify(assistant).registerServiceCallBack(any(), assistantCallbackCaptor.capture())
+        Settings.Secure.putInt(
+            context.contentResolver,
+            BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+            TEST_GROUP_ID1)
+        `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
+        assistantCallbackCaptor.value.sourceAdded(device1, receiveState)
+    }
+
+    private fun triggerSourceRemoved() {
+        verify(assistant).registerServiceCallBack(any(), assistantCallbackCaptor.capture())
+        `when`(assistant.allConnectedDevices).thenReturn(listOf(device1))
+        Settings.Secure.putInt(
+            context.contentResolver,
+            BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+            TEST_GROUP_ID1)
+        assistantCallbackCaptor.value.sourceRemoved(device2)
+    }
+
+    private fun triggerProfileConnectionChange(state: Int, profile: Int) {
+        verify(eventManager).registerCallback(btCallbackCaptor.capture())
+        `when`(assistant.allConnectedDevices).thenReturn(listOf(device1))
+        Settings.Secure.putInt(
+            context.contentResolver,
+            BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+            TEST_GROUP_ID1)
+        btCallbackCaptor.value.onProfileConnectionStateChanged(cachedDevice2, state, profile)
+    }
+
+    private fun triggerContentObserverChange() {
+        verify(contentResolver)
+            .registerContentObserver(
+                eq(Settings.Secure.getUriFor(BluetoothUtils.getPrimaryGroupIdUriForBroadcast())),
+                eq(false),
+                contentObserverCaptor.capture())
+        `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
+        Settings.Secure.putInt(
+            context.contentResolver,
+            BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+            TEST_GROUP_ID2)
+        contentObserverCaptor.value.primaryChanged()
+    }
+
+    private fun triggerVolumeMapChange(change: Pair<BluetoothDevice, Int>) {
+        verify(volumeControl).registerCallback(any(), volumeCallbackCaptor.capture())
+        volumeCallbackCaptor.value.onDeviceVolumeChanged(change.first, change.second)
+    }
+
+    private enum class TriggerType {
+        BROADCAST_START,
+        BROADCAST_STOP
+    }
+
+    private companion object {
+        const val TEST_GROUP_ID_INVALID = -1
+        const val TEST_GROUP_ID1 = 1
+        const val TEST_GROUP_ID2 = 2
+        const val TEST_SOURCE_ID = 1
+        const val TEST_BROADCAST_ID = 1
+        const val TEST_REASON = 1
+        const val TEST_RECEIVE_STATE_CONTENT = 1L
+        const val TEST_VOLUME1 = 10
+        const val TEST_VOLUME2 = 20
+
+        val broadcastStarted: BluetoothLeBroadcast.Callback.() -> Unit = {
+            onBroadcastStarted(TEST_REASON, TEST_BROADCAST_ID)
+        }
+        val broadcastStopped: BluetoothLeBroadcast.Callback.() -> Unit = {
+            onBroadcastStopped(TEST_REASON, TEST_BROADCAST_ID)
+        }
+        val sourceAdded:
+            BluetoothLeBroadcastAssistant.Callback.(
+                sink: BluetoothDevice, state: BluetoothLeBroadcastReceiveState) -> Unit =
+            { sink, state ->
+                onReceiveStateChanged(sink, TEST_SOURCE_ID, state)
+            }
+        val sourceRemoved: BluetoothLeBroadcastAssistant.Callback.(sink: BluetoothDevice) -> Unit =
+            { sink ->
+                onSourceRemoved(sink, TEST_SOURCE_ID, TEST_REASON)
+            }
+        val primaryChanged: ContentObserver.() -> Unit = { onChange(false) }
+    }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 43d51c3..92f03d7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -45,7 +45,7 @@
 import com.android.systemui.communal.shared.model.CommunalBackgroundType
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalTransitionKeys
-import com.android.systemui.communal.ui.compose.Dimensions.SlideOffsetY
+import com.android.systemui.communal.ui.compose.Dimensions.Companion.SlideOffsetY
 import com.android.systemui.communal.ui.compose.extensions.allowGestures
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.util.CommunalColors
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 bd4710b..ef6eec8 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
@@ -16,6 +16,8 @@
 
 package com.android.systemui.communal.ui.compose
 
+import android.content.Context
+import android.content.res.Configuration
 import android.graphics.drawable.Icon
 import android.os.Bundle
 import android.util.SizeF
@@ -116,6 +118,7 @@
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.layout.positionInWindow
+import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.testTag
@@ -129,6 +132,7 @@
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.testTagsAsResourceId
 import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
@@ -388,6 +392,10 @@
     }
 }
 
+val hubDimensions: Dimensions
+    @Composable
+    get() = Dimensions(LocalContext.current, LocalConfiguration.current, LocalDensity.current)
+
 @Composable
 private fun DisclaimerBottomSheetContent(onButtonClicked: () -> Unit) {
     val colors = LocalAndroidColorScheme.current
@@ -509,7 +517,6 @@
                 gridState = gridState,
                 contentListState = contentListState,
                 contentOffset = contentOffset,
-                updateDragPositionForRemove = updateDragPositionForRemove
             )
 
         // A full size box in background that listens to widget drops from the picker.
@@ -517,7 +524,7 @@
         // for android drag events.
         Box(Modifier.fillMaxSize().dragAndDropTarget(dragAndDropTargetState)) {}
     } else {
-        gridModifier = gridModifier.height(Dimensions.GridHeight)
+        gridModifier = gridModifier.height(hubDimensions.GridHeight)
     }
 
     LazyHorizontalGrid(
@@ -595,7 +602,7 @@
 ) {
     val colors = LocalAndroidColorScheme.current
     Card(
-        modifier = Modifier.height(Dimensions.GridHeight).padding(contentPadding),
+        modifier = Modifier.height(hubDimensions.GridHeight).padding(contentPadding),
         colors = CardDefaults.cardColors(containerColor = Color.Transparent),
         border = BorderStroke(3.dp, colors.secondary),
         shape = RoundedCornerShape(size = 80.dp)
@@ -1280,7 +1287,7 @@
         return PaddingValues(
             start = Dimensions.ItemSpacing,
             end = Dimensions.ItemSpacing,
-            top = Dimensions.GridTopSpacing,
+            top = hubDimensions.GridTopSpacing,
         )
     }
     val context = LocalContext.current
@@ -1289,7 +1296,8 @@
     val screenHeight = with(density) { windowMetrics.bounds.height().toDp() }
     val toolbarHeight = with(density) { Dimensions.ToolbarPaddingTop + toolbarSize.height.toDp() }
     val verticalPadding =
-        ((screenHeight - toolbarHeight - Dimensions.GridHeight + Dimensions.GridTopSpacing) / 2)
+        ((screenHeight - toolbarHeight - hubDimensions.GridHeight + hubDimensions.GridTopSpacing) /
+                2)
             .coerceAtLeast(Dimensions.Spacing)
     return PaddingValues(
         start = Dimensions.ToolbarPaddingHorizontal,
@@ -1345,29 +1353,44 @@
     fun toOffset(): Offset = Offset(start, top)
 }
 
-object Dimensions {
-    val CardHeightFull = 530.dp
-    val GridTopSpacing = 114.dp
-    val GridHeight = CardHeightFull + GridTopSpacing
-    val ItemSpacing = 50.dp
-    val CardHeightHalf = (CardHeightFull - ItemSpacing) / 2
-    val CardHeightThird = (CardHeightFull - (2 * ItemSpacing)) / 3
-    val CardWidth = 360.dp
-    val CardOutlineWidth = 3.dp
-    val Spacing = ItemSpacing / 2
+class Dimensions(val context: Context, val config: Configuration, val density: Density) {
+    val GridTopSpacing: Dp
+        get() {
+            if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+                return 114.dp
+            } else {
+                val windowMetrics =
+                    WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
+                val screenHeight = with(density) { windowMetrics.bounds.height().toDp() }
 
-    // The sizing/padding of the toolbar in glanceable hub edit mode
-    val ToolbarPaddingTop = 27.dp
-    val ToolbarPaddingHorizontal = ItemSpacing
-    val ToolbarButtonPaddingHorizontal = 24.dp
-    val ToolbarButtonPaddingVertical = 16.dp
-    val ButtonPadding =
-        PaddingValues(
-            vertical = ToolbarButtonPaddingVertical,
-            horizontal = ToolbarButtonPaddingHorizontal,
-        )
-    val IconSize = 40.dp
-    val SlideOffsetY = 30.dp
+                return (screenHeight - CardHeightFull) / 2
+            }
+        }
+
+    val GridHeight = CardHeightFull + GridTopSpacing
+
+    companion object {
+        val CardHeightFull = 530.dp
+        val ItemSpacing = 50.dp
+        val CardHeightHalf = (CardHeightFull - ItemSpacing) / 2
+        val CardHeightThird = (CardHeightFull - (2 * ItemSpacing)) / 3
+        val CardWidth = 360.dp
+        val CardOutlineWidth = 3.dp
+        val Spacing = ItemSpacing / 2
+
+        // The sizing/padding of the toolbar in glanceable hub edit mode
+        val ToolbarPaddingTop = 27.dp
+        val ToolbarPaddingHorizontal = ItemSpacing
+        val ToolbarButtonPaddingHorizontal = 24.dp
+        val ToolbarButtonPaddingVertical = 16.dp
+        val ButtonPadding =
+            PaddingValues(
+                vertical = ToolbarButtonPaddingVertical,
+                horizontal = ToolbarButtonPaddingHorizontal,
+            )
+        val IconSize = 40.dp
+        val SlideOffsetY = 30.dp
+    }
 }
 
 private object Colors {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
index 9e6f22a..0c29394 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
@@ -18,17 +18,13 @@
 
 import android.content.ClipDescription
 import android.view.DragEvent
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.draganddrop.dragAndDropTarget
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.scrollBy
-import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
 import androidx.compose.foundation.lazy.grid.LazyGridState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.rememberUpdatedState
@@ -45,8 +41,7 @@
 import com.android.systemui.communal.util.WidgetPickerIntentUtils
 import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.isActive
+import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.launch
 
 /**
@@ -59,32 +54,22 @@
     gridState: LazyGridState,
     contentOffset: Offset,
     contentListState: ContentListState,
-    updateDragPositionForRemove: (offset: Offset) -> Boolean,
 ): DragAndDropTargetState {
     val scope = rememberCoroutineScope()
-    val autoScrollSpeed = remember { mutableFloatStateOf(0f) }
-    // Threshold of distance from edges that should start auto-scroll - chosen to be a narrow value
-    // that allows differentiating intention of scrolling from intention of dragging over the first
-    // visible item.
     val autoScrollThreshold = with(LocalDensity.current) { 60.dp.toPx() }
     val state =
-        remember(gridState, contentListState) {
+        remember(gridState, contentOffset, contentListState, autoScrollThreshold, scope) {
             DragAndDropTargetState(
                 state = gridState,
                 contentOffset = contentOffset,
                 contentListState = contentListState,
-                scope = scope,
-                autoScrollSpeed = autoScrollSpeed,
                 autoScrollThreshold = autoScrollThreshold,
-                updateDragPositionForRemove = updateDragPositionForRemove,
+                scope = scope,
             )
         }
-    LaunchedEffect(autoScrollSpeed.floatValue) {
-        if (autoScrollSpeed.floatValue != 0f) {
-            while (isActive) {
-                gridState.scrollBy(autoScrollSpeed.floatValue)
-                delay(10)
-            }
+    LaunchedEffect(state) {
+        for (diff in state.scrollChannel) {
+            gridState.scrollBy(diff)
         }
     }
     return state
@@ -96,7 +81,6 @@
  * @see androidx.compose.foundation.draganddrop.dragAndDropTarget
  * @see DragEvent
  */
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
 internal fun Modifier.dragAndDropTarget(
     dragDropTargetState: DragAndDropTargetState,
@@ -122,6 +106,10 @@
                         return state.onDrop(event)
                     }
 
+                    override fun onExited(event: DragAndDropEvent) {
+                        state.onExited()
+                    }
+
                     override fun onEnded(event: DragAndDropEvent) {
                         state.onEnded()
                     }
@@ -149,19 +137,17 @@
     private val state: LazyGridState,
     private val contentOffset: Offset,
     private val contentListState: ContentListState,
-    private val scope: CoroutineScope,
-    private val autoScrollSpeed: MutableState<Float>,
     private val autoScrollThreshold: Float,
-    private val updateDragPositionForRemove: (offset: Offset) -> Boolean,
+    private val scope: CoroutineScope,
 ) {
     /**
      * The placeholder item that is treated as if it is being dragged across the grid. It is added
      * to grid once drag and drop event is started and removed when event ends.
      */
     private var placeHolder = CommunalContentModel.WidgetPlaceholder()
-
     private var placeHolderIndex: Int? = null
-    private var isOnRemoveButton = false
+
+    internal val scrollChannel = Channel<Float>()
 
     fun onStarted() {
         // assume item will be added to the end.
@@ -170,39 +156,39 @@
     }
 
     fun onMoved(event: DragAndDropEvent) {
-        val dragEvent = event.toAndroidDragEvent()
-        isOnRemoveButton = updateDragPositionForRemove(Offset(dragEvent.x, dragEvent.y))
-        if (!isOnRemoveButton) {
-            findTargetItem(dragEvent)?.apply {
-                var scrollIndex: Int? = null
-                var scrollOffset: Int? = null
-                if (placeHolderIndex == state.firstVisibleItemIndex) {
-                    // Save info about the first item before the move, to neutralize the automatic
-                    // keeping first item first.
-                    scrollIndex = placeHolderIndex
-                    scrollOffset = state.firstVisibleItemScrollOffset
-                }
+        val dragOffset = event.toOffset()
 
-                autoScrollIfNearEdges(dragEvent)
+        val targetItem =
+            state.layoutInfo.visibleItemsInfo
+                .asSequence()
+                .filter { item -> contentListState.isItemEditable(item.index) }
+                .firstItemAtOffset(dragOffset - contentOffset)
 
-                if (contentListState.isItemEditable(this.index)) {
-                    movePlaceholderTo(this.index)
-                    placeHolderIndex = this.index
-                }
-
-                if (scrollIndex != null && scrollOffset != null) {
-                    // this is needed to neutralize automatic keeping the first item first.
-                    scope.launch { state.scrollToItem(scrollIndex, scrollOffset) }
-                }
+        if (targetItem != null) {
+            var scrollIndex: Int? = null
+            var scrollOffset: Int? = null
+            if (placeHolderIndex == state.firstVisibleItemIndex) {
+                // Save info about the first item before the move, to neutralize the automatic
+                // keeping first item first.
+                scrollIndex = placeHolderIndex
+                scrollOffset = state.firstVisibleItemScrollOffset
             }
+
+            if (contentListState.isItemEditable(targetItem.index)) {
+                movePlaceholderTo(targetItem.index)
+                placeHolderIndex = targetItem.index
+            }
+
+            if (scrollIndex != null && scrollOffset != null) {
+                // this is needed to neutralize automatic keeping the first item first.
+                scope.launch { state.scrollToItem(scrollIndex, scrollOffset) }
+            }
+        } else {
+            computeAutoscroll(dragOffset).takeIf { it != 0f }?.let { scrollChannel.trySend(it) }
         }
     }
 
     fun onDrop(event: DragAndDropEvent): Boolean {
-        autoScrollSpeed.value = 0f
-        if (isOnRemoveButton) {
-            return false
-        }
         return placeHolderIndex?.let { dropIndex ->
             val widgetExtra = event.maybeWidgetExtra() ?: return false
             val (componentName, user) = widgetExtra
@@ -221,39 +207,35 @@
     }
 
     fun onEnded() {
-        autoScrollSpeed.value = 0f
         placeHolderIndex = null
         contentListState.list.remove(placeHolder)
-        isOnRemoveButton = updateDragPositionForRemove(Offset.Zero)
     }
 
-    private fun autoScrollIfNearEdges(dragEvent: DragEvent) {
+    fun onExited() {
+        onEnded()
+    }
+
+    private fun computeAutoscroll(dragOffset: Offset): Float {
         val orientation = state.layoutInfo.orientation
         val distanceFromStart =
             if (orientation == Orientation.Horizontal) {
-                dragEvent.x
+                dragOffset.x
             } else {
-                dragEvent.y
+                dragOffset.y
             }
         val distanceFromEnd =
             if (orientation == Orientation.Horizontal) {
-                state.layoutInfo.viewportSize.width - dragEvent.x
+                state.layoutInfo.viewportEndOffset - dragOffset.x
             } else {
-                state.layoutInfo.viewportSize.height - dragEvent.y
+                state.layoutInfo.viewportEndOffset - dragOffset.y
             }
-        autoScrollSpeed.value =
-            when {
-                distanceFromEnd < autoScrollThreshold -> autoScrollThreshold - distanceFromEnd
-                distanceFromStart < autoScrollThreshold ->
-                    -(autoScrollThreshold - distanceFromStart)
-                else -> 0f
-            }
-    }
 
-    private fun findTargetItem(dragEvent: DragEvent): LazyGridItemInfo? =
-        state.layoutInfo.visibleItemsInfo.firstItemAtOffset(
-            Offset(dragEvent.x, dragEvent.y) - contentOffset
-        )
+        return when {
+            distanceFromEnd < autoScrollThreshold -> autoScrollThreshold - distanceFromEnd
+            distanceFromStart < autoScrollThreshold -> distanceFromStart - autoScrollThreshold
+            else -> 0f
+        }
+    }
 
     private fun movePlaceholderTo(index: Int) {
         val currentIndex = contentListState.list.indexOf(placeHolder)
@@ -271,4 +253,6 @@
         val clipData = this.toAndroidDragEvent().clipData.takeIf { it.itemCount != 0 }
         return clipData?.getItemAt(0)?.intent?.let { intent -> getWidgetExtraFromIntent(intent) }
     }
+
+    private fun DragAndDropEvent.toOffset() = this.toAndroidDragEvent().run { Offset(x, y) }
 }
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 a184cf3..776e166 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
@@ -198,6 +198,7 @@
 
     LaunchedEffect(scrollableState.isScrollInProgress) {
         if (!scrollableState.isScrollInProgress && scrollOffset <= minScrollOffset) {
+            viewModel.setHeadsUpAnimatingAway(false)
             viewModel.snoozeHun()
         }
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
index f9f7df8..4f5d0e5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
+import com.android.systemui.biometrics.FaceHelpMessageDebouncer
 import com.android.systemui.biometrics.data.repository.FaceSensorInfo
 import com.android.systemui.biometrics.data.repository.fakeFacePropertyRepository
 import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
@@ -36,7 +37,7 @@
 import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.bouncer.shared.flag.fakeComposeBouncerFlags
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
 import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
 import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
 import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
@@ -75,15 +76,20 @@
     private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
     private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
     private lateinit var underTest: BouncerMessageViewModel
+    private val ignoreHelpMessageId = 1
 
     @Before
     fun setUp() {
         kosmos.fakeUserRepository.setUserInfos(listOf(PRIMARY_USER))
         kosmos.fakeComposeBouncerFlags.composeBouncerEnabled = true
+        overrideResource(
+            R.array.config_face_acquire_device_entry_ignorelist,
+            intArrayOf(ignoreHelpMessageId)
+        )
         underTest = kosmos.bouncerMessageViewModel
         overrideResource(R.string.kg_trust_agent_disabled, "Trust agent is unavailable")
         kosmos.fakeSystemPropertiesHelper.set(
-            DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+            DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
             "not mainline reboot"
         )
     }
@@ -379,7 +385,15 @@
             runCurrent()
 
             kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
-                HelpFaceAuthenticationStatus(1, "some helpful message")
+                HelpFaceAuthenticationStatus(0, "some helpful message", 0)
+            )
+            runCurrent()
+            kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+                HelpFaceAuthenticationStatus(
+                    0,
+                    "some helpful message",
+                    FaceHelpMessageDebouncer.DEFAULT_WINDOW_MS
+                )
             )
             runCurrent()
             assertThat(bouncerMessage?.text).isEqualTo("Enter PIN")
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 2546f27..2bf50b3 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
@@ -52,7 +52,6 @@
 import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
 import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
 import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
-import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
 import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
 import com.android.systemui.display.data.repository.displayRepository
 import com.android.systemui.dump.DumpManager
@@ -79,7 +78,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.res.R
 import com.android.systemui.statusbar.commandQueue
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.testKosmos
@@ -478,29 +476,6 @@
         }
 
     @Test
-    fun faceHelpMessagesAreIgnoredBasedOnConfig() =
-        testScope.runTest {
-            overrideResource(
-                R.array.config_face_acquire_device_entry_ignorelist,
-                intArrayOf(10, 11)
-            )
-            underTest = createDeviceEntryFaceAuthRepositoryImpl()
-            initCollectors()
-            allPreconditionsToRunFaceAuthAreTrue()
-
-            underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
-            faceAuthenticateIsCalled()
-
-            authenticationCallback.value.onAuthenticationHelp(9, "help msg")
-            authenticationCallback.value.onAuthenticationHelp(10, "Ignored help msg")
-            authenticationCallback.value.onAuthenticationHelp(11, "Ignored help msg")
-
-            val response = authStatus() as HelpFaceAuthenticationStatus
-            assertThat(response.msg).isEqualTo("help msg")
-            assertThat(response.msgId).isEqualTo(response.msgId)
-        }
-
-    @Test
     fun dumpDoesNotErrorOutWhenFaceManagerOrBypassControllerIsNull() =
         testScope.runTest {
             fakeUserRepository.setSelectedUserInfo(primaryUser)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 546510b..3253edf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -20,7 +20,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.SceneKey
-import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -32,27 +31,14 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.AdaptiveAuthRequest
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.BouncerLockedOut
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.PolicyLockdown
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.SecurityTimeout
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.TrustAgentDisabled
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UnattendedUpdate
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UserLockdown
 import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.flags.fakeSystemPropertiesHelper
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeTrustRepository
-import com.android.systemui.keyguard.shared.model.AuthenticationFlags
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
@@ -61,7 +47,6 @@
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -438,231 +423,6 @@
             assertThat(isUnlocked).isTrue()
         }
 
-    @Test
-    fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
-        testScope.runTest {
-            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
-            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
-            kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
-            runCurrent()
-
-            verifyRestrictionReasonsForAuthFlags(
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
-                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to null,
-                LockPatternUtils.StrongAuthTracker
-                    .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
-                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
-                    null,
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
-                    null,
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to null
-            )
-        }
-
-    @Test
-    fun deviceEntryRestrictionReason_whenFaceIsEnrolledAndEnabled_mapsToAuthFlagsState() =
-        testScope.runTest {
-            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
-            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
-            kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
-            kosmos.fakeSystemPropertiesHelper.set(
-                DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
-                "not mainline reboot"
-            )
-            runCurrent()
-
-            verifyRestrictionReasonsForAuthFlags(
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
-                    DeviceNotUnlockedSinceReboot,
-                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
-                    AdaptiveAuthRequest,
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
-                    BouncerLockedOut,
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
-                    SecurityTimeout,
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
-                    UserLockdown,
-                LockPatternUtils.StrongAuthTracker
-                    .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
-                    NonStrongBiometricsSecurityTimeout,
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
-                    UnattendedUpdate,
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
-                    PolicyLockdown,
-                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
-                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
-                    null,
-            )
-        }
-
-    @Test
-    fun deviceEntryRestrictionReason_whenFingerprintIsEnrolledAndEnabled_mapsToAuthFlagsState() =
-        testScope.runTest {
-            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
-            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
-            kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
-            kosmos.fakeSystemPropertiesHelper.set(
-                DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
-                "not mainline reboot"
-            )
-            runCurrent()
-
-            verifyRestrictionReasonsForAuthFlags(
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
-                    DeviceNotUnlockedSinceReboot,
-                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
-                    AdaptiveAuthRequest,
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
-                    BouncerLockedOut,
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
-                    SecurityTimeout,
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
-                    UserLockdown,
-                LockPatternUtils.StrongAuthTracker
-                    .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
-                    NonStrongBiometricsSecurityTimeout,
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
-                    UnattendedUpdate,
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
-                    PolicyLockdown,
-                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
-                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
-                    null,
-            )
-        }
-
-    @Test
-    fun deviceEntryRestrictionReason_whenTrustAgentIsEnabled_mapsToAuthFlagsState() =
-        testScope.runTest {
-            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
-            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
-            kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
-            kosmos.fakeTrustRepository.setCurrentUserTrustManaged(false)
-            kosmos.fakeSystemPropertiesHelper.set(
-                DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
-                "not mainline reboot"
-            )
-            runCurrent()
-
-            verifyRestrictionReasonsForAuthFlags(
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
-                    DeviceNotUnlockedSinceReboot,
-                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
-                    AdaptiveAuthRequest,
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
-                    BouncerLockedOut,
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
-                    SecurityTimeout,
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
-                    UserLockdown,
-                LockPatternUtils.StrongAuthTracker
-                    .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
-                    NonStrongBiometricsSecurityTimeout,
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
-                    UnattendedUpdate,
-                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
-                    PolicyLockdown,
-                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
-                    TrustAgentDisabled,
-                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
-                    TrustAgentDisabled,
-            )
-        }
-
-    @Test
-    fun deviceEntryRestrictionReason_whenDeviceRebootedForMainlineUpdate_mapsToTheCorrectReason() =
-        testScope.runTest {
-            val deviceEntryRestrictionReason by
-                collectLastValue(underTest.deviceEntryRestrictionReason)
-            kosmos.fakeSystemPropertiesHelper.set(
-                DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
-                DeviceEntryInteractor.REBOOT_MAINLINE_UPDATE
-            )
-            kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
-                AuthenticationFlags(
-                    userId = 1,
-                    flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
-                )
-            )
-            runCurrent()
-
-            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
-            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
-            kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
-            runCurrent()
-
-            assertThat(deviceEntryRestrictionReason).isNull()
-
-            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
-            runCurrent()
-
-            assertThat(deviceEntryRestrictionReason)
-                .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
-
-            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
-            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
-            runCurrent()
-
-            assertThat(deviceEntryRestrictionReason)
-                .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
-
-            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
-            kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
-            runCurrent()
-
-            assertThat(deviceEntryRestrictionReason)
-                .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
-        }
-
-    @Test
-    fun reportUserPresent_whenDeviceEntered() =
-        testScope.runTest {
-            val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
-            assertThat(isDeviceEntered).isFalse()
-            assertThat(kosmos.fakeDeviceEntryRepository.userPresentCount).isEqualTo(0)
-
-            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
-                SuccessFingerprintAuthenticationStatus(0, true)
-            )
-            runCurrent()
-            switchToScene(Scenes.Gone)
-            assertThat(isDeviceEntered).isTrue()
-            assertThat(kosmos.fakeDeviceEntryRepository.userPresentCount).isEqualTo(1)
-
-            switchToScene(Scenes.Lockscreen)
-            assertThat(isDeviceEntered).isFalse()
-            assertThat(kosmos.fakeDeviceEntryRepository.userPresentCount).isEqualTo(1)
-
-            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
-                SuccessFingerprintAuthenticationStatus(0, true)
-            )
-            switchToScene(Scenes.Gone)
-            assertThat(isDeviceEntered).isTrue()
-            assertThat(kosmos.fakeDeviceEntryRepository.userPresentCount).isEqualTo(2)
-        }
-
-    private fun TestScope.verifyRestrictionReasonsForAuthFlags(
-        vararg authFlagToDeviceEntryRestriction: Pair<Int, DeviceEntryRestrictionReason?>
-    ) {
-        val deviceEntryRestrictionReason by collectLastValue(underTest.deviceEntryRestrictionReason)
-
-        authFlagToDeviceEntryRestriction.forEach { (flag, expectedReason) ->
-            kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
-                AuthenticationFlags(userId = 1, flag = flag)
-            )
-            runCurrent()
-
-            if (expectedReason == null) {
-                assertThat(deviceEntryRestrictionReason).isNull()
-            } else {
-                assertThat(deviceEntryRestrictionReason).isEqualTo(expectedReason)
-            }
-        }
-    }
-
     private fun switchToScene(sceneKey: SceneKey) {
         sceneInteractor.changeScene(sceneKey, "reason")
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
index a7a7bea3..c2acc5f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
@@ -19,17 +19,20 @@
 import android.content.pm.UserInfo
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
-import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
 import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
+import com.android.systemui.flags.fakeSystemPropertiesHelper
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.trustInteractor
+import com.android.systemui.keyguard.shared.model.AuthenticationFlags
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -40,6 +43,7 @@
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -54,18 +58,8 @@
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val authenticationRepository = kosmos.fakeAuthenticationRepository
-    private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
 
-    val underTest =
-        DeviceUnlockedInteractor(
-            applicationScope = testScope.backgroundScope,
-            authenticationInteractor = kosmos.authenticationInteractor,
-            deviceEntryRepository = deviceEntryRepository,
-            trustInteractor = kosmos.trustInteractor,
-            faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor,
-            fingerprintAuthInteractor = kosmos.deviceEntryFingerprintAuthInteractor,
-            powerInteractor = kosmos.powerInteractor,
-        )
+    val underTest = kosmos.deviceUnlockedInteractor
 
     @Before
     fun setup() {
@@ -100,6 +94,30 @@
         }
 
     @Test
+    fun deviceUnlockStatus_whenUnlockedAndAuthMethodIsPinAndInLockdown_isFalse() =
+        testScope.runTest {
+            val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+            val isInLockdown by collectLastValue(underTest.isInLockdown)
+
+            authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+            kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+                AuthenticationFlags(
+                    userId = 1,
+                    flag =
+                        LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
+                )
+            )
+            runCurrent()
+            assertThat(isInLockdown).isTrue()
+
+            assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+            assertThat(deviceUnlockStatus?.deviceUnlockSource).isNull()
+        }
+
+    @Test
     fun deviceUnlockStatus_whenUnlockedAndAuthMethodIsSim_isFalse() =
         testScope.runTest {
             val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
@@ -221,6 +239,218 @@
             assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
         }
 
+    @Test
+    fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
+        testScope.runTest {
+            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+            kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+            runCurrent()
+
+            verifyRestrictionReasonsForAuthFlags(
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
+                LockPatternUtils.StrongAuthTracker
+                    .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+                    null,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+                    null,
+            )
+        }
+
+    @Test
+    fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_whenLockdown() =
+        testScope.runTest {
+            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+            kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+            runCurrent()
+
+            verifyRestrictionReasonsForAuthFlags(
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+                    DeviceEntryRestrictionReason.UserLockdown,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+                    DeviceEntryRestrictionReason.PolicyLockdown
+            )
+        }
+
+    @Test
+    fun deviceEntryRestrictionReason_whenFaceIsEnrolledAndEnabled_mapsToAuthFlagsState() =
+        testScope.runTest {
+            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+            kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+            kosmos.fakeSystemPropertiesHelper.set(
+                DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
+                "not mainline reboot"
+            )
+            runCurrent()
+
+            verifyRestrictionReasonsForAuthFlags(
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+                    DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+                    DeviceEntryRestrictionReason.AdaptiveAuthRequest,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+                    DeviceEntryRestrictionReason.BouncerLockedOut,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+                    DeviceEntryRestrictionReason.SecurityTimeout,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+                    DeviceEntryRestrictionReason.UserLockdown,
+                LockPatternUtils.StrongAuthTracker
+                    .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+                    DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+                    DeviceEntryRestrictionReason.UnattendedUpdate,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+                    DeviceEntryRestrictionReason.PolicyLockdown,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+                    null,
+            )
+        }
+
+    @Test
+    fun deviceEntryRestrictionReason_whenFingerprintIsEnrolledAndEnabled_mapsToAuthFlagsState() =
+        testScope.runTest {
+            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+            kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+            kosmos.fakeSystemPropertiesHelper.set(
+                DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
+                "not mainline reboot"
+            )
+            runCurrent()
+
+            verifyRestrictionReasonsForAuthFlags(
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+                    DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+                    DeviceEntryRestrictionReason.AdaptiveAuthRequest,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+                    DeviceEntryRestrictionReason.BouncerLockedOut,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+                    DeviceEntryRestrictionReason.SecurityTimeout,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+                    DeviceEntryRestrictionReason.UserLockdown,
+                LockPatternUtils.StrongAuthTracker
+                    .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+                    DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+                    DeviceEntryRestrictionReason.UnattendedUpdate,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+                    DeviceEntryRestrictionReason.PolicyLockdown,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+                    null,
+            )
+        }
+
+    @Test
+    fun deviceEntryRestrictionReason_whenTrustAgentIsEnabled_mapsToAuthFlagsState() =
+        testScope.runTest {
+            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+            kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
+            kosmos.fakeTrustRepository.setCurrentUserTrustManaged(false)
+            kosmos.fakeSystemPropertiesHelper.set(
+                DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
+                "not mainline reboot"
+            )
+            runCurrent()
+
+            verifyRestrictionReasonsForAuthFlags(
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+                    DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+                    DeviceEntryRestrictionReason.AdaptiveAuthRequest,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+                    DeviceEntryRestrictionReason.BouncerLockedOut,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+                    DeviceEntryRestrictionReason.SecurityTimeout,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+                    DeviceEntryRestrictionReason.UserLockdown,
+                LockPatternUtils.StrongAuthTracker
+                    .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+                    DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+                    DeviceEntryRestrictionReason.UnattendedUpdate,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+                    DeviceEntryRestrictionReason.PolicyLockdown,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
+                    DeviceEntryRestrictionReason.TrustAgentDisabled,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+                    DeviceEntryRestrictionReason.TrustAgentDisabled,
+            )
+        }
+
+    @Test
+    fun deviceEntryRestrictionReason_whenDeviceRebootedForMainlineUpdate_mapsToTheCorrectReason() =
+        testScope.runTest {
+            val deviceEntryRestrictionReason by
+                collectLastValue(underTest.deviceEntryRestrictionReason)
+            kosmos.fakeSystemPropertiesHelper.set(
+                DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
+                DeviceUnlockedInteractor.REBOOT_MAINLINE_UPDATE
+            )
+            kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+                AuthenticationFlags(
+                    userId = 1,
+                    flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+                )
+            )
+            runCurrent()
+
+            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+            kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+            runCurrent()
+
+            assertThat(deviceEntryRestrictionReason).isNull()
+
+            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+            runCurrent()
+
+            assertThat(deviceEntryRestrictionReason)
+                .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+
+            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+            runCurrent()
+
+            assertThat(deviceEntryRestrictionReason)
+                .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+
+            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+            kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
+            runCurrent()
+
+            assertThat(deviceEntryRestrictionReason)
+                .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+        }
+
+    private fun TestScope.verifyRestrictionReasonsForAuthFlags(
+        vararg authFlagToDeviceEntryRestriction: Pair<Int, DeviceEntryRestrictionReason?>
+    ) {
+        val deviceEntryRestrictionReason by collectLastValue(underTest.deviceEntryRestrictionReason)
+
+        authFlagToDeviceEntryRestriction.forEach { (flag, expectedReason) ->
+            kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+                AuthenticationFlags(userId = 1, flag = flag)
+            )
+            runCurrent()
+
+            if (expectedReason == null) {
+                assertThat(deviceEntryRestrictionReason).isNull()
+            } else {
+                assertThat(deviceEntryRestrictionReason).isEqualTo(expectedReason)
+            }
+        }
+    }
+
     companion object {
         private const val primaryUserId = 1
         private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY)
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
new file mode 100644
index 0000000..142631e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.data.repository.audioSharingRepository
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class AudioSharingInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    lateinit var underTest: AudioSharingInteractor
+
+    @Before
+    fun setUp() {
+        with(kosmos) { underTest = audioSharingInteractor }
+    }
+
+    @Test
+    fun volumeChanges_returnVolume() {
+        with(kosmos) {
+            testScope.runTest {
+                with(audioSharingRepository) {
+                    setSecondaryGroupId(TEST_GROUP_ID)
+                    setVolumeMap(mapOf(TEST_GROUP_ID to TEST_VOLUME))
+                }
+                val volume by collectLastValue(underTest.volume)
+                runCurrent()
+
+                Truth.assertThat(volume).isEqualTo(TEST_VOLUME)
+            }
+        }
+    }
+
+    @Test
+    fun volumeChanges_returnNull() {
+        with(kosmos) {
+            testScope.runTest {
+                with(audioSharingRepository) {
+                    setSecondaryGroupId(TEST_GROUP_ID_INVALID)
+                    setVolumeMap(mapOf(TEST_GROUP_ID to TEST_VOLUME))
+                }
+                val volume by collectLastValue(underTest.volume)
+                runCurrent()
+
+                Truth.assertThat(volume).isNull()
+            }
+        }
+    }
+
+    @Test
+    fun volumeChanges_returnDefaultVolume() {
+        with(kosmos) {
+            testScope.runTest {
+                with(audioSharingRepository) {
+                    setSecondaryGroupId(TEST_GROUP_ID)
+                    setVolumeMap(emptyMap())
+                }
+                val volume by collectLastValue(underTest.volume)
+                runCurrent()
+
+                Truth.assertThat(volume).isEqualTo(TEST_VOLUME_DEFAULT)
+            }
+        }
+    }
+
+    private companion object {
+        const val TEST_GROUP_ID = 1
+        const val TEST_GROUP_ID_INVALID = -1
+        const val TEST_VOLUME = 10
+        const val TEST_VOLUME_DEFAULT = 20
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
index 56273eb..6e25744 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
@@ -33,6 +33,7 @@
 import android.view.animation.PathInterpolator;
 import android.widget.FrameLayout;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.assist.AssistLogger;
@@ -66,7 +67,7 @@
     protected InvocationLightsView mInvocationLightsView;
     protected final AssistLogger mAssistLogger;
 
-    private final WindowManager mWindowManager;
+    private final ViewCaptureAwareWindowManager mWindowManager;
     private final MetricsLogger mMetricsLogger;
     private final Lazy<AssistManager> mAssistManagerLazy;
     private final WindowManager.LayoutParams mLayoutParams;
@@ -80,12 +81,12 @@
 
     @Inject
     public DefaultUiController(Context context, AssistLogger assistLogger,
-            WindowManager windowManager, MetricsLogger metricsLogger,
-            Lazy<AssistManager> assistManagerLazy,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
+            MetricsLogger metricsLogger, Lazy<AssistManager> assistManagerLazy,
             NavigationBarController navigationBarController) {
         mAssistLogger = assistLogger;
         mRoot = new FrameLayout(context);
-        mWindowManager = windowManager;
+        mWindowManager = viewCaptureAwareWindowManager;
         mMetricsLogger = metricsLogger;
         mAssistManagerLazy = assistManagerLazy;
 
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index a9f985f..468737d 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -288,6 +288,7 @@
     override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
         withContext(backgroundDispatcher) {
             if (isSuccessful) {
+                lockPatternUtils.userPresent(selectedUserId)
                 lockPatternUtils.reportSuccessfulPasswordAttempt(selectedUserId)
                 _hasLockoutOccurred.value = false
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDebouncer.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDebouncer.kt
new file mode 100644
index 0000000..1685f49
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDebouncer.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.biometrics
+
+import android.util.Log
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
+
+/**
+ * Debounces face help messages with parameters:
+ * - window: Window of time (in milliseconds) to analyze face acquired messages)
+ * - startWindow: Window of time on start required before showing the first help message
+ * - shownFaceMessageFrequencyBoost: Frequency boost given to messages that are currently shown to
+ *   the user
+ */
+class FaceHelpMessageDebouncer(
+    private val window: Long = DEFAULT_WINDOW_MS,
+    private val startWindow: Long = window,
+    private val shownFaceMessageFrequencyBoost: Int = 4,
+) {
+    private val TAG = "FaceHelpMessageDebouncer"
+    private var startTime = 0L
+    private var helpFaceAuthStatuses: MutableList<HelpFaceAuthenticationStatus> = mutableListOf()
+    private var lastMessageIdShown: Int? = null
+
+    /** Remove messages that are outside of the time [window]. */
+    private fun removeOldMessages(currTimestamp: Long) {
+        var numToRemove = 0
+        // This works under the assumption that timestamps are ordered from first to last
+        // in chronological order
+        for (index in helpFaceAuthStatuses.indices) {
+            if ((helpFaceAuthStatuses[index].createdAt + window) >= currTimestamp) {
+                break // all timestamps from here and on are within the window
+            }
+            numToRemove += 1
+        }
+
+        // Remove all outside time window
+        repeat(numToRemove) { helpFaceAuthStatuses.removeFirst() }
+
+        if (numToRemove > 0) {
+            Log.v(TAG, "removedFirst=$numToRemove")
+        }
+    }
+
+    private fun getMostFrequentHelpMessage(): HelpFaceAuthenticationStatus? {
+        // freqMap: msgId => frequency
+        val freqMap = helpFaceAuthStatuses.groupingBy { it.msgId }.eachCount().toMutableMap()
+
+        // Give shownFaceMessageFrequencyBoost to lastMessageIdShown
+        if (lastMessageIdShown != null) {
+            freqMap.computeIfPresent(lastMessageIdShown!!) { _, value ->
+                value + shownFaceMessageFrequencyBoost
+            }
+        }
+        // Go through all msgId keys & find the highest frequency msgId
+        val msgIdWithHighestFrequency =
+            freqMap.entries
+                .maxWithOrNull { (msgId1, freq1), (msgId2, freq2) ->
+                    // ties are broken by more recent message
+                    if (freq1 == freq2) {
+                        helpFaceAuthStatuses
+                            .findLast { it.msgId == msgId1 }!!
+                            .createdAt
+                            .compareTo(
+                                helpFaceAuthStatuses.findLast { it.msgId == msgId2 }!!.createdAt
+                            )
+                    } else {
+                        freq1.compareTo(freq2)
+                    }
+                }
+                ?.key
+        return helpFaceAuthStatuses.findLast { it.msgId == msgIdWithHighestFrequency }
+    }
+
+    fun addMessage(helpFaceAuthStatus: HelpFaceAuthenticationStatus) {
+        helpFaceAuthStatuses.add(helpFaceAuthStatus)
+        Log.v(TAG, "added message=$helpFaceAuthStatus")
+    }
+
+    fun getMessageToShow(atTimestamp: Long): HelpFaceAuthenticationStatus? {
+        if (helpFaceAuthStatuses.isEmpty() || (atTimestamp - startTime) < startWindow) {
+            // there's not enough time that has passed to determine whether to show anything yet
+            Log.v(TAG, "No message; haven't made initial threshold window OR no messages")
+            return null
+        }
+        removeOldMessages(atTimestamp)
+        val messageToShow = getMostFrequentHelpMessage()
+        if (lastMessageIdShown != messageToShow?.msgId) {
+            Log.v(
+                TAG,
+                "showMessage previousLastMessageId=$lastMessageIdShown" +
+                    "\n\tmessageToShow=$messageToShow " +
+                    "\n\thelpFaceAuthStatusesSize=${helpFaceAuthStatuses.size}" +
+                    "\n\thelpFaceAuthStatuses=$helpFaceAuthStatuses"
+            )
+            lastMessageIdShown = messageToShow?.msgId
+        }
+        return messageToShow
+    }
+
+    fun startNewFaceAuthSession(faceAuthStartedTime: Long) {
+        Log.d(TAG, "startNewFaceAuthSession at startTime=$startTime")
+        startTime = faceAuthStartedTime
+        helpFaceAuthStatuses.clear()
+        lastMessageIdShown = null
+    }
+
+    companion object {
+        const val DEFAULT_WINDOW_MS = 200L
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index c868d01..430887d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -94,8 +94,16 @@
 
         val textColorError =
             view.resources.getColor(R.color.biometric_dialog_error, view.context.theme)
+
+        val attributes =
+            view.context.obtainStyledAttributes(
+                R.style.TextAppearance_AuthCredential_Indicator,
+                intArrayOf(android.R.attr.textColor)
+            )
         val textColorHint =
-            view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme)
+            if (constraintBp()) attributes.getColor(0, 0)
+            else view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme)
+        attributes.recycle()
 
         val logoView = view.requireViewById<ImageView>(R.id.logo)
         val logoDescriptionView = view.requireViewById<TextView>(R.id.logo_description)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
index 6cb9b16..810b6d1 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
@@ -32,7 +32,7 @@
 import com.android.systemui.deviceentry.domain.interactor.BiometricMessageInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
 import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
 import com.android.systemui.deviceentry.shared.model.FaceFailureMessage
 import com.android.systemui.deviceentry.shared.model.FaceLockoutMessage
@@ -75,7 +75,7 @@
     private val clock: SystemClock,
     private val biometricMessageInteractor: BiometricMessageInteractor,
     private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
-    private val deviceEntryInteractor: DeviceEntryInteractor,
+    private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
     private val fingerprintInteractor: DeviceEntryFingerprintAuthInteractor,
     flags: ComposeBouncerFlags,
 ) {
@@ -119,7 +119,7 @@
                         }
                     } else if (authMethod.isSecure) {
                         combine(
-                            deviceEntryInteractor.deviceEntryRestrictionReason,
+                            deviceUnlockedInteractor.deviceEntryRestrictionReason,
                             lockoutMessage,
                             fingerprintInteractor.isFingerprintCurrentlyAllowedOnBouncer,
                             resetToDefault,
@@ -413,7 +413,7 @@
         clock: SystemClock,
         biometricMessageInteractor: BiometricMessageInteractor,
         faceAuthInteractor: DeviceEntryFaceAuthInteractor,
-        deviceEntryInteractor: DeviceEntryInteractor,
+        deviceUnlockedInteractor: DeviceUnlockedInteractor,
         fingerprintInteractor: DeviceEntryFingerprintAuthInteractor,
         flags: ComposeBouncerFlags,
         userSwitcherViewModel: UserSwitcherViewModel,
@@ -427,7 +427,7 @@
             clock = clock,
             biometricMessageInteractor = biometricMessageInteractor,
             faceAuthInteractor = faceAuthInteractor,
-            deviceEntryInteractor = deviceEntryInteractor,
+            deviceUnlockedInteractor = deviceUnlockedInteractor,
             fingerprintInteractor = fingerprintInteractor,
             flags = flags,
             selectedUser = userSwitcherViewModel.selectedUser,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index fa52dad..9460eaf 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -57,16 +57,13 @@
 import com.android.systemui.log.SessionTracker
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.UserRepository
 import com.google.errorprone.annotations.CompileTimeConstant
 import java.io.PrintWriter
-import java.util.Arrays
 import java.util.concurrent.Executor
-import java.util.stream.Collectors
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -170,7 +167,6 @@
 ) : DeviceEntryFaceAuthRepository, Dumpable {
     private var authCancellationSignal: CancellationSignal? = null
     private var detectCancellationSignal: CancellationSignal? = null
-    private var faceAcquiredInfoIgnoreList: Set<Int>
     private var retryCount = 0
 
     private var pendingAuthenticateRequest = MutableStateFlow<AuthenticationRequest?>(null)
@@ -240,14 +236,6 @@
             faceManager?.addLockoutResetCallback(faceLockoutResetCallback)
             faceAuthLogger.addLockoutResetCallbackDone()
         }
-        faceAcquiredInfoIgnoreList =
-            Arrays.stream(
-                    context.resources.getIntArray(
-                        R.array.config_face_acquire_device_entry_ignorelist
-                    )
-                )
-                .boxed()
-                .collect(Collectors.toSet())
         dumpManager.registerCriticalDumpable("DeviceEntryFaceAuthRepositoryImpl", this)
 
         canRunFaceAuth =
@@ -485,10 +473,8 @@
             }
 
             override fun onAuthenticationHelp(code: Int, helpStr: CharSequence?) {
-                if (faceAcquiredInfoIgnoreList.contains(code)) {
-                    return
-                }
-                _authenticationStatus.value = HelpFaceAuthenticationStatus(code, helpStr.toString())
+                _authenticationStatus.value =
+                    HelpFaceAuthenticationStatus(code, helpStr?.toString())
             }
 
             override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) {
@@ -731,7 +717,6 @@
         pw.println("  _pendingAuthenticateRequest: ${pendingAuthenticateRequest.value}")
         pw.println("  authCancellationSignal: $authCancellationSignal")
         pw.println("  detectCancellationSignal: $detectCancellationSignal")
-        pw.println("  faceAcquiredInfoIgnoreList: $faceAcquiredInfoIgnoreList")
         pw.println("  _authenticationStatus: ${_authenticationStatus.value}")
         pw.println("  _detectionStatus: ${_detectionStatus.value}")
         pw.println("  currentUserId: $currentUserId")
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
index 0f18978..e2ad774 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -39,12 +39,6 @@
      * the lockscreen.
      */
     val isBypassEnabled: StateFlow<Boolean>
-
-    /**
-     * Reports, to system server, that the user is "present" now. This is a signal that system
-     * server uses to know that the device has been entered.
-     */
-    suspend fun reportUserPresent()
 }
 
 /** Encapsulates application state for device entry. */
@@ -84,17 +78,6 @@
                 SharingStarted.Eagerly,
                 initialValue = keyguardBypassController.bypassEnabled,
             )
-
-    override suspend fun reportUserPresent() {
-        withContext(backgroundDispatcher) {
-            val selectedUserId = userRepository.selectedUser.value.userInfo.id
-            lockPatternUtils.userPresent(selectedUserId)
-        }
-    }
-
-    companion object {
-        private const val TAG = "DeviceEntryRepositoryImpl"
-    }
 }
 
 @Module
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractor.kt
new file mode 100644
index 0000000..34b1544
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractor.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import android.content.res.Resources
+import android.hardware.biometrics.BiometricFaceConstants
+import com.android.systemui.biometrics.FaceHelpMessageDebouncer
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.deviceentry.shared.model.AcquiredFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.res.R
+import java.util.Arrays
+import java.util.stream.Collectors
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transform
+
+/**
+ * Process face authentication statuses.
+ * - Ignores face help messages based on R.array.config_face_acquire_device_entry_ignorelist.
+ * - Uses FaceHelpMessageDebouncer to debounce flickery help messages.
+ */
+@SysUISingleton
+class DeviceEntryFaceAuthStatusInteractor
+@Inject
+constructor(
+    repository: DeviceEntryFaceAuthRepository,
+    @Main private val resources: Resources,
+    @Application private val applicationScope: CoroutineScope,
+) {
+    private val faceHelpMessageDebouncer = FaceHelpMessageDebouncer()
+    private var faceAcquiredInfoIgnoreList: Set<Int> =
+        Arrays.stream(resources.getIntArray(R.array.config_face_acquire_device_entry_ignorelist))
+            .boxed()
+            .collect(Collectors.toSet())
+
+    val authenticationStatus: StateFlow<FaceAuthenticationStatus?> =
+        repository.authenticationStatus
+            .transform { authenticationStatus ->
+                if (authenticationStatus is AcquiredFaceAuthenticationStatus) {
+                    if (
+                        authenticationStatus.acquiredInfo ==
+                            BiometricFaceConstants.FACE_ACQUIRED_START
+                    ) {
+                        faceHelpMessageDebouncer.startNewFaceAuthSession(
+                            authenticationStatus.createdAt
+                        )
+                    }
+                }
+
+                if (authenticationStatus is HelpFaceAuthenticationStatus) {
+                    if (!faceAcquiredInfoIgnoreList.contains(authenticationStatus.msgId)) {
+                        faceHelpMessageDebouncer.addMessage(authenticationStatus)
+                    }
+
+                    val messageToShow =
+                        faceHelpMessageDebouncer.getMessageToShow(
+                            atTimestamp = authenticationStatus.createdAt,
+                        )
+                    if (messageToShow != null) {
+                        emit(messageToShow)
+                    }
+
+                    return@transform
+                }
+
+                emit(authenticationStatus)
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = null,
+            )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 425bb96..ea0e59b 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -16,33 +16,24 @@
 
 package com.android.systemui.deviceentry.domain.interactor
 
-import androidx.annotation.VisibleForTesting
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
-import com.android.systemui.flags.SystemPropertiesHelper
-import com.android.systemui.keyguard.domain.interactor.TrustInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.util.kotlin.Quad
 import com.android.systemui.utils.coroutines.flow.mapLatestConflated
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
@@ -61,12 +52,7 @@
     private val repository: DeviceEntryRepository,
     private val authenticationInteractor: AuthenticationInteractor,
     private val sceneInteractor: SceneInteractor,
-    faceAuthInteractor: DeviceEntryFaceAuthInteractor,
-    private val fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
-    private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
-    private val trustInteractor: TrustInteractor,
     private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
-    private val systemPropertiesHelper: SystemPropertiesHelper,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
 ) {
     /**
@@ -109,11 +95,6 @@
                     false
                 }
             }
-            .onEach { isDeviceEntered ->
-                if (isDeviceEntered) {
-                    repository.reportUserPresent()
-                }
-            }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
@@ -156,70 +137,6 @@
                 initialValue = null,
             )
 
-    private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
-    private val fingerprintEnrolledAndEnabled =
-        biometricSettingsInteractor.isFingerprintAuthEnrolledAndEnabled
-    private val trustAgentEnabled = trustInteractor.isEnrolledAndEnabled
-
-    private val faceOrFingerprintOrTrustEnabled: Flow<Triple<Boolean, Boolean, Boolean>> =
-        combine(faceEnrolledAndEnabled, fingerprintEnrolledAndEnabled, trustAgentEnabled, ::Triple)
-
-    /**
-     * Reason why device entry is restricted to certain authentication methods for the current user.
-     *
-     * Emits null when there are no device entry restrictions active.
-     */
-    val deviceEntryRestrictionReason: Flow<DeviceEntryRestrictionReason?> =
-        faceOrFingerprintOrTrustEnabled.flatMapLatest {
-            (faceEnabled, fingerprintEnabled, trustEnabled) ->
-            if (faceEnabled || fingerprintEnabled || trustEnabled) {
-                combine(
-                        biometricSettingsInteractor.authenticationFlags,
-                        faceAuthInteractor.isLockedOut,
-                        fingerprintAuthInteractor.isLockedOut,
-                        trustInteractor.isTrustAgentCurrentlyAllowed,
-                        ::Quad
-                    )
-                    .map { (authFlags, isFaceLockedOut, isFingerprintLockedOut, trustManaged) ->
-                        when {
-                            authFlags.isPrimaryAuthRequiredAfterReboot &&
-                                wasRebootedForMainlineUpdate ->
-                                DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate
-                            authFlags.isPrimaryAuthRequiredAfterReboot ->
-                                DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
-                            authFlags.isPrimaryAuthRequiredAfterDpmLockdown ->
-                                DeviceEntryRestrictionReason.PolicyLockdown
-                            authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown
-                            authFlags.isPrimaryAuthRequiredForUnattendedUpdate ->
-                                DeviceEntryRestrictionReason.UnattendedUpdate
-                            authFlags.isPrimaryAuthRequiredAfterTimeout ->
-                                DeviceEntryRestrictionReason.SecurityTimeout
-                            authFlags.isPrimaryAuthRequiredAfterLockout ->
-                                DeviceEntryRestrictionReason.BouncerLockedOut
-                            isFingerprintLockedOut ->
-                                DeviceEntryRestrictionReason.StrongBiometricsLockedOut
-                            isFaceLockedOut && faceAuthInteractor.isFaceAuthStrong() ->
-                                DeviceEntryRestrictionReason.StrongBiometricsLockedOut
-                            isFaceLockedOut -> DeviceEntryRestrictionReason.NonStrongFaceLockedOut
-                            authFlags.isSomeAuthRequiredAfterAdaptiveAuthRequest ->
-                                DeviceEntryRestrictionReason.AdaptiveAuthRequest
-                            (trustEnabled && !trustManaged) &&
-                                (authFlags.someAuthRequiredAfterTrustAgentExpired ||
-                                    authFlags.someAuthRequiredAfterUserRequest) ->
-                                DeviceEntryRestrictionReason.TrustAgentDisabled
-                            authFlags.strongerAuthRequiredAfterNonStrongBiometricsTimeout ->
-                                DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
-                            else -> null
-                        }
-                    }
-            } else {
-                flowOf(null)
-            }
-        }
-
-    /** Whether the device is in lockdown mode, where bouncer input is required to unlock. */
-    val isInLockdown: Flow<Boolean> = deviceEntryRestrictionReason.map { it.isInLockdown() }
-
     /**
      * Attempt to enter the device and dismiss the lockscreen. If authentication is required to
      * unlock the device it will transition to bouncer.
@@ -268,27 +185,6 @@
         return repository.isLockscreenEnabled()
     }
 
-    fun DeviceEntryRestrictionReason?.isInLockdown(): Boolean {
-        return when (this) {
-            DeviceEntryRestrictionReason.UserLockdown -> true
-            DeviceEntryRestrictionReason.PolicyLockdown -> true
-
-            // Add individual enum value instead of using "else" so new reasons are guaranteed
-            // to be added here at compile-time.
-            null -> false
-            DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot -> false
-            DeviceEntryRestrictionReason.BouncerLockedOut -> false
-            DeviceEntryRestrictionReason.AdaptiveAuthRequest -> false
-            DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout -> false
-            DeviceEntryRestrictionReason.TrustAgentDisabled -> false
-            DeviceEntryRestrictionReason.StrongBiometricsLockedOut -> false
-            DeviceEntryRestrictionReason.SecurityTimeout -> false
-            DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate -> false
-            DeviceEntryRestrictionReason.UnattendedUpdate -> false
-            DeviceEntryRestrictionReason.NonStrongFaceLockedOut -> false
-        }
-    }
-
     /**
      * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically
      * dismissed once the authentication challenge is completed. For example, completing a biometric
@@ -296,12 +192,4 @@
      * lockscreen.
      */
     val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled
-
-    private val wasRebootedForMainlineUpdate
-        get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
-
-    companion object {
-        @VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last"
-        @VisibleForTesting const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
index 5141690..e17e530 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
@@ -16,20 +16,26 @@
 
 package com.android.systemui.deviceentry.domain.interactor
 
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
 import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
 import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
+import com.android.systemui.flags.SystemPropertiesHelper
 import com.android.systemui.keyguard.domain.interactor.TrustInteractor
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
@@ -49,6 +55,8 @@
     faceAuthInteractor: DeviceEntryFaceAuthInteractor,
     fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
     private val powerInteractor: PowerInteractor,
+    private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
+    private val systemPropertiesHelper: SystemPropertiesHelper,
 ) {
 
     private val deviceUnlockSource =
@@ -69,6 +77,75 @@
                 .map { DeviceUnlockSource.BouncerInput }
         )
 
+    private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
+    private val fingerprintEnrolledAndEnabled =
+        biometricSettingsInteractor.isFingerprintAuthEnrolledAndEnabled
+    private val trustAgentEnabled = trustInteractor.isEnrolledAndEnabled
+
+    private val faceOrFingerprintOrTrustEnabled: Flow<Triple<Boolean, Boolean, Boolean>> =
+        combine(faceEnrolledAndEnabled, fingerprintEnrolledAndEnabled, trustAgentEnabled, ::Triple)
+
+    /**
+     * Reason why device entry is restricted to certain authentication methods for the current user.
+     *
+     * Emits null when there are no device entry restrictions active.
+     */
+    val deviceEntryRestrictionReason: Flow<DeviceEntryRestrictionReason?> =
+        faceOrFingerprintOrTrustEnabled.flatMapLatest {
+            (faceEnabled, fingerprintEnabled, trustEnabled) ->
+            if (faceEnabled || fingerprintEnabled || trustEnabled) {
+                combine(
+                    biometricSettingsInteractor.authenticationFlags,
+                    faceAuthInteractor.isLockedOut,
+                    fingerprintAuthInteractor.isLockedOut,
+                    trustInteractor.isTrustAgentCurrentlyAllowed,
+                ) { authFlags, isFaceLockedOut, isFingerprintLockedOut, trustManaged ->
+                    when {
+                        authFlags.isPrimaryAuthRequiredAfterReboot &&
+                            wasRebootedForMainlineUpdate() ->
+                            DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate
+                        authFlags.isPrimaryAuthRequiredAfterReboot ->
+                            DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
+                        authFlags.isPrimaryAuthRequiredAfterDpmLockdown ->
+                            DeviceEntryRestrictionReason.PolicyLockdown
+                        authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown
+                        authFlags.isPrimaryAuthRequiredForUnattendedUpdate ->
+                            DeviceEntryRestrictionReason.UnattendedUpdate
+                        authFlags.isPrimaryAuthRequiredAfterTimeout ->
+                            DeviceEntryRestrictionReason.SecurityTimeout
+                        authFlags.isPrimaryAuthRequiredAfterLockout ->
+                            DeviceEntryRestrictionReason.BouncerLockedOut
+                        isFingerprintLockedOut ->
+                            DeviceEntryRestrictionReason.StrongBiometricsLockedOut
+                        isFaceLockedOut && faceAuthInteractor.isFaceAuthStrong() ->
+                            DeviceEntryRestrictionReason.StrongBiometricsLockedOut
+                        isFaceLockedOut -> DeviceEntryRestrictionReason.NonStrongFaceLockedOut
+                        authFlags.isSomeAuthRequiredAfterAdaptiveAuthRequest ->
+                            DeviceEntryRestrictionReason.AdaptiveAuthRequest
+                        (trustEnabled && !trustManaged) &&
+                            (authFlags.someAuthRequiredAfterTrustAgentExpired ||
+                                authFlags.someAuthRequiredAfterUserRequest) ->
+                            DeviceEntryRestrictionReason.TrustAgentDisabled
+                        authFlags.strongerAuthRequiredAfterNonStrongBiometricsTimeout ->
+                            DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
+                        else -> null
+                    }
+                }
+            } else {
+                biometricSettingsInteractor.authenticationFlags.map { authFlags ->
+                    when {
+                        authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown
+                        authFlags.isPrimaryAuthRequiredAfterDpmLockdown ->
+                            DeviceEntryRestrictionReason.PolicyLockdown
+                        else -> null
+                    }
+                }
+            }
+        }
+
+    /** Whether the device is in lockdown mode, where bouncer input is required to unlock. */
+    val isInLockdown: Flow<Boolean> = deviceEntryRestrictionReason.map { it.isInLockdown() }
+
     /**
      * Whether the device is unlocked or not, along with the information about the authentication
      * method that was used to unlock the device.
@@ -90,13 +167,18 @@
                     // Device is locked if SIM is locked.
                     flowOf(DeviceUnlockStatus(false, null))
                 } else {
-                    powerInteractor.isAsleep.flatMapLatest { isAsleep ->
-                        if (isAsleep) {
-                            flowOf(DeviceUnlockStatus(false, null))
-                        } else {
-                            deviceUnlockSource.map { DeviceUnlockStatus(true, it) }
+                    combine(
+                            powerInteractor.isAsleep,
+                            isInLockdown,
+                            ::Pair,
+                        )
+                        .flatMapLatestConflated { (isAsleep, isInLockdown) ->
+                            if (isAsleep || isInLockdown) {
+                                flowOf(DeviceUnlockStatus(false, null))
+                            } else {
+                                deviceUnlockSource.map { DeviceUnlockStatus(true, it) }
+                            }
                         }
-                    }
                 }
             }
             .stateIn(
@@ -104,4 +186,34 @@
                 started = SharingStarted.Eagerly,
                 initialValue = DeviceUnlockStatus(false, null),
             )
+
+    private fun DeviceEntryRestrictionReason?.isInLockdown(): Boolean {
+        return when (this) {
+            DeviceEntryRestrictionReason.UserLockdown -> true
+            DeviceEntryRestrictionReason.PolicyLockdown -> true
+
+            // Add individual enum value instead of using "else" so new reasons are guaranteed
+            // to be added here at compile-time.
+            null -> false
+            DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot -> false
+            DeviceEntryRestrictionReason.BouncerLockedOut -> false
+            DeviceEntryRestrictionReason.AdaptiveAuthRequest -> false
+            DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout -> false
+            DeviceEntryRestrictionReason.TrustAgentDisabled -> false
+            DeviceEntryRestrictionReason.StrongBiometricsLockedOut -> false
+            DeviceEntryRestrictionReason.SecurityTimeout -> false
+            DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate -> false
+            DeviceEntryRestrictionReason.UnattendedUpdate -> false
+            DeviceEntryRestrictionReason.NonStrongFaceLockedOut -> false
+        }
+    }
+
+    private fun wasRebootedForMainlineUpdate(): Boolean {
+        return systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
+    }
+
+    companion object {
+        @VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last"
+        @VisibleForTesting const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index d12ea45..c536d6b 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -90,6 +90,7 @@
     private val powerInteractor: PowerInteractor,
     private val biometricSettingsRepository: BiometricSettingsRepository,
     private val trustManager: TrustManager,
+    deviceEntryFaceAuthStatusInteractor: DeviceEntryFaceAuthStatusInteractor,
 ) : DeviceEntryFaceAuthInteractor {
 
     private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
@@ -276,9 +277,13 @@
     }
 
     private val faceAuthenticationStatusOverride = MutableStateFlow<FaceAuthenticationStatus?>(null)
+
     /** Provide the status of face authentication */
     override val authenticationStatus =
-        merge(faceAuthenticationStatusOverride.filterNotNull(), repository.authenticationStatus)
+        merge(
+            faceAuthenticationStatusOverride.filterNotNull(),
+            deviceEntryFaceAuthStatusInteractor.authenticationStatus.filterNotNull(),
+        )
 
     /** Provide the status of face detection */
     override val detectionStatus = repository.detectionStatus
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index bbc3d76..89c7178 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -168,8 +168,7 @@
      */
     @Deprecated("Will be merged into maybeStartTransitionToOccludedOrInsecureCamera")
     suspend fun maybeHandleInsecurePowerGesture(): Boolean {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
-        if (SceneContainerFlag.isEnabled) return true
+        if (SceneContainerFlag.isEnabled) return false
         if (keyguardOcclusionInteractor.shouldTransitionFromPowerButtonGesture()) {
             if (keyguardInteractor.isKeyguardDismissible.value) {
                 startTransitionTo(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
index 853f176..1fd609d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
@@ -26,6 +26,7 @@
 import android.view.WindowManager
 import android.widget.ProgressBar
 import androidx.core.view.isGone
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.res.R
 import javax.inject.Inject
@@ -37,7 +38,7 @@
 @Inject
 constructor(
     private val layoutInflater: LayoutInflater,
-    private val windowManager: WindowManager,
+    private val windowManager: ViewCaptureAwareWindowManager,
 ) {
     private var overlayView: View? = null
 
@@ -90,7 +91,7 @@
     ) {
         if (overlayView == null) {
             overlayView = layoutInflater.inflate(R.layout.sidefps_progress_bar, null, false)
-            windowManager.addView(overlayView, overlayViewParams)
+            windowManager.addView(requireNotNull(overlayView), overlayViewParams)
             progressBar?.pivotX = 0.0f
             progressBar?.pivotY = 0.0f
         }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index 18358a7..d8c13b6 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -33,6 +33,7 @@
 import androidx.core.os.postDelayed
 import androidx.core.view.isVisible
 import androidx.dynamicanimation.animation.DynamicAnimation
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
 import com.android.internal.jank.Cuj
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.util.LatencyTracker
@@ -83,7 +84,7 @@
 class BackPanelController
 internal constructor(
     context: Context,
-    private val windowManager: WindowManager,
+    private val windowManager: ViewCaptureAwareWindowManager,
     private val viewConfiguration: ViewConfiguration,
     private val mainHandler: Handler,
     private val systemClock: SystemClock,
@@ -102,7 +103,7 @@
     class Factory
     @Inject
     constructor(
-        private val windowManager: WindowManager,
+        private val windowManager: ViewCaptureAwareWindowManager,
         private val viewConfiguration: ViewConfiguration,
         @BackPanelUiThread private val uiThreadContext: UiThreadContext,
         private val systemClock: SystemClock,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index c9be993..947336d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -91,6 +91,7 @@
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.util.concurrency.BackPanelUiThread;
 import com.android.systemui.util.concurrency.UiThreadContext;
@@ -297,6 +298,7 @@
     private Date mTmpLogDate = new Date();
 
     private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
 
     private final NavigationEdgeBackPlugin.BackCallback mBackCallback =
             new NavigationEdgeBackPlugin.BackCallback() {
@@ -423,7 +425,8 @@
             Optional<DesktopMode> desktopModeOptional,
             FalsingManager falsingManager,
             Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider,
-            Provider<LightBarController> lightBarControllerProvider) {
+            Provider<LightBarController> lightBarControllerProvider,
+            NotificationShadeWindowController notificationShadeWindowController) {
         mContext = context;
         mDisplayId = context.getDisplayId();
         mUiThreadContext = uiThreadContext;
@@ -479,6 +482,7 @@
                 this::onNavigationSettingsChanged);
 
         updateCurrentUserResources();
+        mNotificationShadeWindowController = notificationShadeWindowController;
     }
 
     public void setStateChangeCallback(Runnable callback) {
@@ -1297,6 +1301,9 @@
         mBackAnimation.setPilferPointerCallback(() -> {
             pilferPointers();
         });
+        mBackAnimation.setTopUiRequestCallback(
+                (requestTopUi, tag) -> mUiThreadContext.getExecutor().execute(() ->
+                        mNotificationShadeWindowController.setRequestTopUi(requestTopUi, tag)));
         updateBackAnimationThresholds();
         if (mLightBarControllerProvider.get() != null) {
             mBackAnimation.setStatusBarCustomizer((appearance) -> {
@@ -1333,6 +1340,7 @@
         private final Provider<BackGestureTfClassifierProvider>
                 mBackGestureTfClassifierProviderProvider;
         private final Provider<LightBarController> mLightBarControllerProvider;
+        private final NotificationShadeWindowController mNotificationShadeWindowController;
 
         @Inject
         public Factory(OverviewProxyService overviewProxyService,
@@ -1353,7 +1361,8 @@
                         FalsingManager falsingManager,
                         Provider<BackGestureTfClassifierProvider>
                                 backGestureTfClassifierProviderProvider,
-                        Provider<LightBarController> lightBarControllerProvider) {
+                Provider<LightBarController> lightBarControllerProvider,
+                NotificationShadeWindowController notificationShadeWindowController) {
             mOverviewProxyService = overviewProxyService;
             mSysUiState = sysUiState;
             mPluginManager = pluginManager;
@@ -1372,6 +1381,7 @@
             mFalsingManager = falsingManager;
             mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
             mLightBarControllerProvider = lightBarControllerProvider;
+            mNotificationShadeWindowController = notificationShadeWindowController;
         }
 
         /** Construct a {@link EdgeBackGestureHandler}. */
@@ -1396,7 +1406,8 @@
                             mDesktopModeOptional,
                             mFalsingManager,
                             mBackGestureTfClassifierProviderProvider,
-                            mLightBarControllerProvider));
+                            mLightBarControllerProvider,
+                            mNotificationShadeWindowController));
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
index 0f36097..1dbd500 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
@@ -19,7 +19,6 @@
 import static android.inputmethodservice.InputMethodService.canImeRenderGesturalNavButtons;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
 
-import static com.android.systemui.Flags.enableViewCaptureTracing;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SEARCH_DISABLED;
@@ -38,7 +37,6 @@
 import android.graphics.Canvas;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.media.permission.SafeCloseable;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.util.AttributeSet;
@@ -61,19 +59,18 @@
 import androidx.annotation.Nullable;
 
 import com.android.app.animation.Interpolators;
-import com.android.app.viewcapture.ViewCaptureFactory;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.Utils;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.ScreenPinningNotify;
+import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
 import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher;
 import com.android.systemui.navigationbar.views.buttons.ContextualButton;
 import com.android.systemui.navigationbar.views.buttons.ContextualButtonGroup;
 import com.android.systemui.navigationbar.views.buttons.DeadZone;
 import com.android.systemui.navigationbar.views.buttons.KeyButtonDrawable;
 import com.android.systemui.navigationbar.views.buttons.NearestTouchFrame;
-import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.res.R;
 import com.android.systemui.settings.DisplayTracker;
@@ -181,7 +178,6 @@
     private boolean mOverviewProxyEnabled;
     private boolean mShowSwipeUpUi;
     private UpdateActiveTouchRegionsCallback mUpdateActiveTouchRegionsCallback;
-    private SafeCloseable mViewCaptureCloseable;
 
     private class NavTransitionListener implements TransitionListener {
         private boolean mBackTransitioning;
@@ -1082,10 +1078,6 @@
         }
 
         updateNavButtonIcons();
-        if (enableViewCaptureTracing()) {
-            mViewCaptureCloseable = ViewCaptureFactory.getInstance(getContext())
-                    .startCapture(getRootView(), ".NavigationBarView");
-        }
     }
 
     @Override
@@ -1098,9 +1090,6 @@
             mFloatingRotationButton.hide();
             mRotationButtonController.unregisterListeners();
         }
-        if (mViewCaptureCloseable != null) {
-            mViewCaptureCloseable.close();
-        }
     }
 
     void dump(PrintWriter pw) {
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 df7430a..158eb6e 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
@@ -65,6 +65,7 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.annotation.WorkerThread;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.logging.UiEventLogger;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -184,7 +185,7 @@
     private GlobalSettings mGlobalSettings;
     private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private ConnectivityManager.NetworkCallback mConnectivityManagerNetworkCallback;
-    private WindowManager mWindowManager;
+    private ViewCaptureAwareWindowManager mWindowManager;
     private ToastFactory mToastFactory;
     private SignalDrawable mSignalDrawable;
     private SignalDrawable mSecondarySignalDrawable; // For the secondary mobile data sub in DSDS
@@ -246,7 +247,7 @@
             @Main Handler handler, @Main Executor mainExecutor,
             BroadcastDispatcher broadcastDispatcher, KeyguardUpdateMonitor keyguardUpdateMonitor,
             GlobalSettings globalSettings, KeyguardStateController keyguardStateController,
-            WindowManager windowManager, ToastFactory toastFactory,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager, ToastFactory toastFactory,
             @Background Handler workerHandler,
             CarrierConfigTracker carrierConfigTracker,
             LocationController locationController,
@@ -278,7 +279,7 @@
         mAccessPointController = accessPointController;
         mWifiIconInjector = new WifiUtils.InternetIconInjector(mContext);
         mConnectivityManagerNetworkCallback = new DataConnectivityListener();
-        mWindowManager = windowManager;
+        mWindowManager = viewCaptureAwareWindowManager;
         mToastFactory = toastFactory;
         mSignalDrawable = new SignalDrawable(mContext);
         mSecondarySignalDrawable = new SignalDrawable(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 1f4820a..8711e88 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -665,7 +665,7 @@
             keyguardEnabledInteractor.isKeyguardEnabled
                 .sample(
                     combine(
-                        deviceEntryInteractor.isInLockdown,
+                        deviceUnlockedInteractor.isInLockdown,
                         deviceEntryInteractor.isDeviceEntered,
                         ::Pair,
                     )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
index abf258c..693cc4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
@@ -485,9 +485,8 @@
             final boolean intersectsTopCutout = topDisplayCutout.intersects(
                     width - (windowWidth / 2), 0,
                     width + (windowWidth / 2), topDisplayCutout.bottom);
-            if (mClingWindow != null &&
-                    (windowWidth < 0 || (width > 0 && intersectsTopCutout))) {
-                final View iconView = mClingWindow.findViewById(R.id.immersive_cling_icon);
+            if (windowWidth < 0 || (width > 0 && intersectsTopCutout)) {
+                final View iconView = findViewById(R.id.immersive_cling_icon);
                 RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)
                         iconView.getLayoutParams();
                 lp.topMargin = topDisplayCutout.bottom;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 1789ad6..2081adc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -3519,7 +3519,7 @@
     // Only when scene container is enabled, mark that we are being dragged so that we start
     // dispatching the rest of the gesture to scene container.
     void startOverscrollAfterExpanding() {
-        SceneContainerFlag.isUnexpectedlyInLegacyMode();
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
         getExpandHelper().finishExpanding();
         setIsBeingDragged(true);
     }
@@ -3527,7 +3527,7 @@
     // Only when scene container is enabled, mark that we are being dragged so that we start
     // dispatching the rest of the gesture to scene container.
     void startDraggingOnHun() {
-        SceneContainerFlag.isUnexpectedlyInLegacyMode();
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
         setIsBeingDragged(true);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 726fdee..a072ea6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -2034,6 +2034,7 @@
                 hunWantsIt = mHeadsUpTouchHelper.onInterceptTouchEvent(ev);
                 if (hunWantsIt) {
                     mView.startDraggingOnHun();
+                    mHeadsUpManager.unpinAll(true);
                 }
             }
             boolean swipeWantsIt = false;
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 eb1f778..1a7bc16 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
@@ -110,6 +110,11 @@
         interactor.setScrolledToTop(scrolledToTop)
     }
 
+    /** Sets whether the heads up notification is animating away. */
+    fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
+        headsUpNotificationInteractor.setHeadsUpAnimatingAway(animatingAway)
+    }
+
     /** Snooze the currently pinned HUN. */
     fun snoozeHun() {
         headsUpNotificationInteractor.snooze()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index 7f16e18..26bd7ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -124,7 +124,6 @@
                     mPanel.setHeadsUpDraggingStartingHeight(startHeight);
                     mPanel.startExpand(x, y, true /* startTracking */, startHeight);
 
-                    // TODO(b/340514839): Figure out where to move this side effect in flexiglass
                     if (!SceneContainerFlag.isEnabled()) {
                         // This call needs to be after the expansion start otherwise we will get a
                         // flicker of one frame as it's not expanded yet.
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 43ab337..40799583 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -91,7 +91,11 @@
     }
 
     /** Run or delay Runnable for given HeadsUpEntry */
-    fun update(entry: HeadsUpEntry?, runnable: Runnable, label: String) {
+    fun update(entry: HeadsUpEntry?, runnable: Runnable?, label: String) {
+        if (runnable == null) {
+            log { "Runnable is NULL, stop update." }
+            return
+        }
         if (!NotificationThrottleHun.isEnabled) {
             runnable.run()
             return
@@ -147,7 +151,11 @@
      * Run or ignore Runnable for given HeadsUpEntry. If entry was never shown, ignore and delete
      * all Runnables associated with that entry.
      */
-    fun delete(entry: HeadsUpEntry?, runnable: Runnable, label: String) {
+    fun delete(entry: HeadsUpEntry?, runnable: Runnable?, label: String) {
+        if (runnable == null) {
+            log { "Runnable is NULL, stop delete." }
+            return
+        }
         if (!NotificationThrottleHun.isEnabled) {
             runnable.run()
             return
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 de8b9b1..eb2f71a1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -73,10 +73,19 @@
         @Provides
         @SysUISingleton
         fun provideAudioSharingRepository(
+            @Application context: Context,
+            contentResolver: ContentResolver,
             localBluetoothManager: LocalBluetoothManager?,
+            @Application coroutineScope: CoroutineScope,
             @Background coroutineContext: CoroutineContext,
         ): AudioSharingRepository =
-            AudioSharingRepositoryImpl(localBluetoothManager, coroutineContext)
+            AudioSharingRepositoryImpl(
+                context,
+                contentResolver,
+                localBluetoothManager,
+                coroutineScope,
+                coroutineContext
+            )
 
         @Provides
         @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt
new file mode 100644
index 0000000..2904092
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractor
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractorEmptyImpl
+import dagger.Module
+import dagger.Provides
+
+/** Dagger module for empty audio sharing impl for unnecessary volume overlay */
+@Module
+interface AudioSharingEmptyImplModule {
+
+    companion object {
+        @Provides
+        @SysUISingleton
+        fun provideAudioSharingInteractor(): AudioSharingInteractor =
+            AudioSharingInteractorEmptyImpl()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingModule.kt
new file mode 100644
index 0000000..9f1e60e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingModule.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.dagger
+
+import com.android.settingslib.volume.data.repository.AudioSharingRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractor
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractorImpl
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineScope
+
+/** Dagger module for audio sharing code in the volume package */
+@Module
+interface AudioSharingModule {
+
+    companion object {
+        @Provides
+        @SysUISingleton
+        fun provideAudioSharingInteractor(
+            @Application coroutineScope: CoroutineScope,
+            repository: AudioSharingRepository
+        ): AudioSharingInteractor = AudioSharingInteractorImpl(coroutineScope, repository)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 5420988..ebb9ce9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -59,6 +59,7 @@
 @Module(
         includes = {
                 AudioModule.class,
+                AudioSharingModule.class,
                 AncModule.class,
                 CaptioningModule.class,
                 MediaDevicesModule.class,
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
new file mode 100644
index 0000000..4d29788
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.domain.interactor
+
+import android.bluetooth.BluetoothCsipSetCoordinator
+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.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.launch
+
+interface AudioSharingInteractor {
+    /** Audio sharing secondary headset volume changes. */
+    val volume: Flow<Int?>
+
+    /** Audio sharing secondary headset min volume. */
+    val volumeMin: Int
+
+    /** Audio sharing secondary headset max volume. */
+    val volumeMax: Int
+
+    /** Set the volume of the secondary headset in audio sharing. */
+    fun setStreamVolume(
+        @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
+        level: Int
+    )
+}
+
+@SysUISingleton
+class AudioSharingInteractorImpl
+@Inject
+constructor(
+    @Application private val coroutineScope: CoroutineScope,
+    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)
+        }
+
+    override val volumeMin: Int = AUDIO_SHARING_VOLUME_MIN
+
+    override val volumeMax: Int = AUDIO_SHARING_VOLUME_MAX
+
+    override fun setStreamVolume(level: Int) {
+        coroutineScope.launch { audioSharingRepository.setSecondaryVolume(level) }
+    }
+
+    private companion object {
+        const val DEFAULT_VOLUME = 20
+    }
+}
+
+@SysUISingleton
+class AudioSharingInteractorEmptyImpl : AudioSharingInteractor {
+    override val volume: Flow<Int?> = emptyFlow()
+    override val volumeMin: Int = EMPTY_VOLUME
+    override val volumeMax: Int = EMPTY_VOLUME
+
+    override fun setStreamVolume(level: Int) {}
+
+    private companion object {
+        const val EMPTY_VOLUME = 0
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt
new file mode 100644
index 0000000..baef620
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt
@@ -0,0 +1,221 @@
+/*
+ * 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.biometrics
+
+import android.hardware.biometrics.BiometricFaceConstants
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class FaceHelpMessageDebouncerTest : SysuiTestCase() {
+    private lateinit var underTest: FaceHelpMessageDebouncer
+    private val window = 9L
+    private val startWindow = 4L
+    private val shownFaceMessageFrequencyBoost = 2
+
+    @Before
+    fun setUp() {
+        underTest =
+            FaceHelpMessageDebouncer(
+                window = window,
+                startWindow = startWindow,
+                shownFaceMessageFrequencyBoost = shownFaceMessageFrequencyBoost,
+            )
+    }
+
+    @Test
+    fun getMessageBeforeStartWindow_null() {
+        underTest.addMessage(
+            HelpFaceAuthenticationStatus(
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+                "testTooClose",
+                0
+            )
+        )
+        assertThat(underTest.getMessageToShow(0)).isNull()
+    }
+
+    @Test
+    fun getMessageAfterStartWindow() {
+        underTest.addMessage(
+            HelpFaceAuthenticationStatus(
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+                "tooClose",
+                0
+            )
+        )
+
+        assertThat(underTest.getMessageToShow(startWindow)?.msgId)
+            .isEqualTo(BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE)
+        assertThat(underTest.getMessageToShow(startWindow)?.msg).isEqualTo("tooClose")
+    }
+
+    @Test
+    fun getMessageAfterMessagesCleared_null() {
+        underTest.addMessage(
+            HelpFaceAuthenticationStatus(
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+                "tooClose",
+                0
+            )
+        )
+        underTest.startNewFaceAuthSession(0)
+
+        assertThat(underTest.getMessageToShow(startWindow)).isNull()
+    }
+
+    @Test
+    fun messagesBeforeWindowRemoved() {
+        underTest.addMessage(
+            HelpFaceAuthenticationStatus(
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+                "tooClose",
+                0
+            )
+        )
+        underTest.addMessage(
+            HelpFaceAuthenticationStatus(
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+                "tooClose",
+                0
+            )
+        )
+        underTest.addMessage(
+            HelpFaceAuthenticationStatus(
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+                "tooClose",
+                window - 1
+            )
+        )
+        val lastMessage =
+            HelpFaceAuthenticationStatus(
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT,
+                "tooBright",
+                window
+            )
+        underTest.addMessage(lastMessage)
+
+        assertThat(underTest.getMessageToShow(window + 1)).isEqualTo(lastMessage)
+    }
+
+    @Test
+    fun getMessageTieGoesToMostRecent() {
+        for (i in 1..window step 2) {
+            underTest.addMessage(
+                HelpFaceAuthenticationStatus(
+                    BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+                    "tooClose",
+                    i
+                )
+            )
+            underTest.addMessage(
+                HelpFaceAuthenticationStatus(
+                    BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT,
+                    "tooBright",
+                    i + 1
+                )
+            )
+        }
+
+        assertThat(underTest.getMessageToShow(window)?.msgId)
+            .isEqualTo(BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT)
+        assertThat(underTest.getMessageToShow(window)?.msg).isEqualTo("tooBright")
+    }
+
+    @Test
+    fun boostCurrentlyShowingMessage() {
+        underTest.addMessage(
+            HelpFaceAuthenticationStatus(
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT,
+                "tooBright",
+                0
+            )
+        )
+
+        val lastMessageShown = underTest.getMessageToShow(startWindow)
+        assertThat(lastMessageShown?.msgId)
+            .isEqualTo(BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT)
+
+        for (i in 1..<shownFaceMessageFrequencyBoost) {
+            underTest.addMessage(
+                HelpFaceAuthenticationStatus(
+                    BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+                    "tooClose",
+                    startWindow
+                )
+            )
+        }
+
+        // although technically there's a different msgId with a higher frequency count now, the
+        // shownFaceMessageFrequencyBoost causes the last message shown to get a "boost"
+        // to keep showing
+        assertThat(underTest.getMessageToShow(startWindow)).isEqualTo(lastMessageShown)
+    }
+
+    @Test
+    fun overcomeBoostedCurrentlyShowingMessage() {
+        // Comments are assuming shownFaceMessageFrequencyBoost = 2
+        // [B], weights: B=1
+        underTest.addMessage(
+            HelpFaceAuthenticationStatus(
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT,
+                "tooBright",
+                0
+            )
+        )
+
+        // [B], showing messageB, weights: B=3
+        val messageB = underTest.getMessageToShow(startWindow)
+
+        // [B, C, C], showing messageB, weights: B=3, C=2
+        for (i in 1..shownFaceMessageFrequencyBoost) {
+            underTest.addMessage(
+                HelpFaceAuthenticationStatus(
+                    BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+                    "tooClose",
+                    startWindow
+                )
+            )
+        }
+        // messageB is getting boosted to continue to show
+        assertThat(underTest.getMessageToShow(startWindow)).isEqualTo(messageB)
+
+        // receive one more FACE_ACQUIRED_TOO_CLOSE acquired info to pass the boost
+        // [C, C, C], showing messageB, weights: B=2, C=3
+        underTest.addMessage(
+            HelpFaceAuthenticationStatus(
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+                "tooClose",
+                startWindow
+            )
+        )
+
+        // Now FACE_ACQUIRED_TOO_CLOSE has surpassed the boosted messageB frequency
+        // [C, C, C], showing messageC, weights: C=5
+        assertThat(underTest.getMessageToShow(startWindow)?.msgId)
+            .isEqualTo(BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt
index 431fef6..6fd8660 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt
@@ -24,6 +24,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.FaceHelpMessageDebouncer
 import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
 import com.android.systemui.biometrics.domain.faceHelpMessageDeferral
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
@@ -45,6 +46,7 @@
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -264,11 +266,20 @@
             biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
             biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
 
-            // WHEN authentication status help
+            // WHEN authentication status help past debouncer
             faceAuthRepository.setAuthenticationStatus(
                 HelpFaceAuthenticationStatus(
                     msg = "Move left",
                     msgId = FACE_ACQUIRED_TOO_RIGHT,
+                    createdAt = 0L,
+                )
+            )
+            runCurrent()
+            faceAuthRepository.setAuthenticationStatus(
+                HelpFaceAuthenticationStatus(
+                    msg = "Move left",
+                    msgId = FACE_ACQUIRED_TOO_RIGHT,
+                    createdAt = FaceHelpMessageDebouncer.DEFAULT_WINDOW_MS,
                 )
             )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index 529cd6e..0b7a3ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -90,6 +90,7 @@
     private val keyguardUpdateMonitor = kosmos.keyguardUpdateMonitor
     private val faceWakeUpTriggersConfig = kosmos.fakeFaceWakeUpTriggersConfig
     private val trustManager = kosmos.trustManager
+    private val deviceEntryFaceAuthStatusInteractor = kosmos.deviceEntryFaceAuthStatusInteractor
 
     @Before
     fun setup() {
@@ -112,6 +113,7 @@
                 powerInteractor,
                 fakeBiometricSettingsRepository,
                 trustManager,
+                deviceEntryFaceAuthStatusInteractor,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorTest.kt
new file mode 100644
index 0000000..6022d9c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorTest.kt
@@ -0,0 +1,160 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import android.hardware.biometrics.BiometricFaceConstants
+import android.hardware.face.FaceManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.FaceHelpMessageDebouncer.Companion.DEFAULT_WINDOW_MS
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.shared.model.AcquiredFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntryFaceAuthStatusInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope: TestScope = kosmos.testScope
+    private lateinit var underTest: DeviceEntryFaceAuthStatusInteractor
+    private val ignoreHelpMessageId = 1
+
+    @Before
+    fun setup() {
+        overrideResource(
+            R.array.config_face_acquire_device_entry_ignorelist,
+            intArrayOf(ignoreHelpMessageId)
+        )
+        underTest = kosmos.deviceEntryFaceAuthStatusInteractor
+    }
+
+    @Test
+    fun successAuthenticationStatus() =
+        testScope.runTest {
+            val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+            val successStatus =
+                SuccessFaceAuthenticationStatus(
+                    successResult = mock(FaceManager.AuthenticationResult::class.java)
+                )
+            kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(successStatus)
+            assertThat(authenticationStatus).isEqualTo(successStatus)
+        }
+
+    @Test
+    fun acquiredFaceAuthenticationStatus() =
+        testScope.runTest {
+            val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+            val acquiredStatus = AcquiredFaceAuthenticationStatus(acquiredInfo = 0)
+            kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(acquiredStatus)
+            assertThat(authenticationStatus).isEqualTo(acquiredStatus)
+        }
+
+    @Test
+    fun failedFaceAuthenticationStatus() =
+        testScope.runTest {
+            val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+            val failedStatus = FailedFaceAuthenticationStatus()
+            kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(failedStatus)
+            assertThat(authenticationStatus).isEqualTo(failedStatus)
+        }
+
+    @Test
+    fun errorFaceAuthenticationStatus() =
+        testScope.runTest {
+            val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+            val errorStatus = ErrorFaceAuthenticationStatus(0, "test")
+            kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(errorStatus)
+            assertThat(authenticationStatus).isEqualTo(errorStatus)
+        }
+
+    @Test
+    fun firstHelpFaceAuthenticationStatus_noUpdate() =
+        testScope.runTest {
+            val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+            kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+                AcquiredFaceAuthenticationStatus(
+                    BiometricFaceConstants.FACE_ACQUIRED_START,
+                    createdAt = 0
+                )
+            )
+            val helpMessage = HelpFaceAuthenticationStatus(0, "test", 1)
+            kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(helpMessage)
+            assertThat(authenticationStatus).isNull()
+        }
+
+    @Test
+    fun helpFaceAuthenticationStatus_afterWindow() =
+        testScope.runTest {
+            val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+            kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+                HelpFaceAuthenticationStatus(0, "test1", 0)
+            )
+            runCurrent()
+            val helpMessage = HelpFaceAuthenticationStatus(0, "test2", DEFAULT_WINDOW_MS)
+            kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(helpMessage)
+            runCurrent()
+            assertThat(authenticationStatus).isEqualTo(helpMessage)
+        }
+
+    @Test
+    fun helpFaceAuthenticationStatus_onlyIgnoredHelpMessages_afterWindow() =
+        testScope.runTest {
+            val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+            kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+                HelpFaceAuthenticationStatus(ignoreHelpMessageId, "ignoredMsg", 0)
+            )
+            runCurrent()
+            kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+                HelpFaceAuthenticationStatus(ignoreHelpMessageId, "ignoredMsg", DEFAULT_WINDOW_MS)
+            )
+            runCurrent()
+            assertThat(authenticationStatus).isNull()
+        }
+
+    @Test
+    fun helpFaceAuthenticationStatus_afterWindow_onIgnoredMessage_showsOtherMessageInstead() =
+        testScope.runTest {
+            val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+            val validHelpMessage = HelpFaceAuthenticationStatus(0, "validHelpMsg", 0)
+            kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(validHelpMessage)
+            runCurrent()
+            // help message that should be ignored
+            kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+                HelpFaceAuthenticationStatus(ignoreHelpMessageId, "ignoredMsg", DEFAULT_WINDOW_MS)
+            )
+            runCurrent()
+            assertThat(authenticationStatus).isEqualTo(validHelpMessage)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
index b169cc1..b4db6da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -27,6 +27,7 @@
 import android.view.WindowManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
 import com.android.internal.jank.Cuj
 import com.android.internal.util.LatencyTracker
 import com.android.systemui.SysuiTestCase
@@ -63,7 +64,7 @@
     private var triggerThreshold: Float = 0.0f
     private val touchSlop = ViewConfiguration.get(context).scaledEdgeSlop
     @Mock private lateinit var vibratorHelper: VibratorHelper
-    @Mock private lateinit var windowManager: WindowManager
+    @Mock private lateinit var viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager
     @Mock private lateinit var configurationController: ConfigurationController
     @Mock private lateinit var latencyTracker: LatencyTracker
     private val interactionJankMonitor by lazy { kosmos.interactionJankMonitor }
@@ -78,7 +79,7 @@
         mBackPanelController =
             BackPanelController(
                 context,
-                windowManager,
+                viewCaptureAwareWindowManager,
                 ViewConfiguration.get(context),
                 Handler.createAsync(testableLooper.looper),
                 systemClock,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
index 5273495..eea02ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
@@ -61,6 +61,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.logging.UiEventLogger;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.settingslib.wifi.WifiUtils;
@@ -160,7 +161,7 @@
     @Mock
     InternetDialogController.InternetDialogCallback mInternetDialogCallback;
     @Mock
-    private WindowManager mWindowManager;
+    private ViewCaptureAwareWindowManager mWindowManager;
     @Mock
     private ToastFactory mToastFactory;
     @Mock
@@ -232,8 +233,9 @@
                 mSubscriptionManager, mTelephonyManager, mWifiManager,
                 mConnectivityManager, mHandler, mExecutor, mBroadcastDispatcher,
                 mock(KeyguardUpdateMonitor.class), mGlobalSettings, mKeyguardStateController,
-                mWindowManager, mToastFactory, mWorkerHandler, mCarrierConfigTracker,
-                mLocationController, mDialogTransitionAnimator, mWifiStateWorker, mFlags);
+                mWindowManager, mToastFactory, mWorkerHandler,
+                mCarrierConfigTracker, mLocationController, mDialogTransitionAnimator,
+                mWifiStateWorker, mFlags);
         mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
                 mInternetDialogController.mOnSubscriptionsChangedListener);
         mInternetDialogController.onStart(mInternetDialogCallback, true);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
index 4b64416..de5f0f3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.deviceentry.domain.interactor.biometricMessageInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel
@@ -44,7 +44,7 @@
             clock = systemClock,
             biometricMessageInteractor = biometricMessageInteractor,
             faceAuthInteractor = deviceEntryFaceAuthInteractor,
-            deviceEntryInteractor = deviceEntryInteractor,
+            deviceUnlockedInteractor = deviceUnlockedInteractor,
             fingerprintInteractor = deviceEntryFingerprintAuthInteractor,
             flags = composeBouncerFlags,
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
index 25e7729..045bd5d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -30,16 +30,10 @@
     private val _isBypassEnabled = MutableStateFlow(false)
     override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled
 
-    var userPresentCount = 0
-
     override suspend fun isLockscreenEnabled(): Boolean {
         return isLockscreenEnabled
     }
 
-    override suspend fun reportUserPresent() {
-        userPresentCount++
-    }
-
     fun setLockscreenEnabled(isLockscreenEnabled: Boolean) {
         this.isLockscreenEnabled = isLockscreenEnabled
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
index a8fc27a..b9be04d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
@@ -57,5 +57,6 @@
             powerInteractor = powerInteractor,
             biometricSettingsRepository = biometricSettingsRepository,
             trustManager = trustManager,
+            deviceEntryFaceAuthStatusInteractor = deviceEntryFaceAuthStatusInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorKosmos.kt
new file mode 100644
index 0000000..66d3709
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import android.content.res.mainResources
+import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.deviceEntryFaceAuthStatusInteractor by
+    Kosmos.Fixture {
+        DeviceEntryFaceAuthStatusInteractor(
+            repository = deviceEntryFaceAuthRepository,
+            resources = mainResources,
+            applicationScope = applicationCoroutineScope,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
index 1200866..caa6e99 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
@@ -19,8 +19,6 @@
 import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
 import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
-import com.android.systemui.flags.fakeSystemPropertiesHelper
-import com.android.systemui.keyguard.domain.interactor.trustInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -34,12 +32,7 @@
             repository = deviceEntryRepository,
             authenticationInteractor = authenticationInteractor,
             sceneInteractor = sceneInteractor,
-            faceAuthInteractor = deviceEntryFaceAuthInteractor,
-            fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
-            biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
-            trustInteractor = trustInteractor,
             deviceUnlockedInteractor = deviceUnlockedInteractor,
-            systemPropertiesHelper = fakeSystemPropertiesHelper,
             alternateBouncerInteractor = alternateBouncerInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
index 14210bc..1ed10fbe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
@@ -18,6 +18,8 @@
 
 import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
+import com.android.systemui.flags.fakeSystemPropertiesHelper
+import com.android.systemui.flags.systemPropertiesHelper
 import com.android.systemui.keyguard.domain.interactor.trustInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -33,5 +35,7 @@
         faceAuthInteractor = deviceEntryFaceAuthInteractor,
         fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
         powerInteractor = powerInteractor,
+        biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
+        systemPropertiesHelper = fakeSystemPropertiesHelper,
     )
 }
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 327e1b5..d391750 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,16 +16,40 @@
 
 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
+import kotlinx.coroutines.flow.StateFlow
 
 class FakeAudioSharingRepository : AudioSharingRepository {
     private val mutableInAudioSharing: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    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 secondaryGroupId: StateFlow<Int> = mutableSecondaryGroupId
+    override val volumeMap: StateFlow<GroupIdToVolumes> = mutableVolumeMap
+
+    override suspend fun setSecondaryVolume(volume: Int) {}
 
     fun setInAudioSharing(state: Boolean) {
         mutableInAudioSharing.value = state
     }
+
+    fun setSecondaryGroupId(groupId: Int) {
+        mutableSecondaryGroupId.value = groupId
+    }
+
+    fun setVolumeMap(volumeMap: GroupIdToVolumes) {
+        mutableVolumeMap.value = volumeMap
+    }
+
+    private companion object {
+        const val TEST_GROUP_ID_INVALID = -1
+    }
 }
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
new file mode 100644
index 0000000..03981bb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorKosmos.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.volume.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.volume.data.repository.audioSharingRepository
+
+val Kosmos.audioSharingInteractor by
+    Kosmos.Fixture {
+        AudioSharingInteractorImpl(
+            applicationCoroutineScope,
+            audioSharingRepository,
+        )
+    }
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
index e031eb2..8a1fe62 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
@@ -30,7 +30,6 @@
         return RavenwoodRuntimeNative.lseek(fd, offset, whence);
     }
 
-
     public static FileDescriptor[] pipe2(int flags) throws ErrnoException {
         return RavenwoodRuntimeNative.pipe2(flags);
     }
@@ -42,4 +41,16 @@
     public static int fcntlInt(FileDescriptor fd, int cmd, int arg) throws ErrnoException {
         return RavenwoodRuntimeNative.fcntlInt(fd, cmd, arg);
     }
+
+    public static StructStat fstat(FileDescriptor fd) throws ErrnoException {
+        return RavenwoodRuntimeNative.fstat(fd);
+    }
+
+    public static StructStat lstat(String path) throws ErrnoException {
+        return RavenwoodRuntimeNative.lstat(path);
+    }
+
+    public static StructStat stat(String path) throws ErrnoException {
+        return RavenwoodRuntimeNative.stat(path);
+    }
 }
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/StructStat.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/StructStat.java
new file mode 100644
index 0000000..a8b1fca
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/StructStat.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2011 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 android.system;
+
+import libcore.util.Objects;
+
+/**
+ * File information returned by {@link Os#fstat}, {@link Os#lstat}, and {@link Os#stat}.
+ * Corresponds to C's {@code struct stat} from {@code <stat.h>}.
+ */
+public final class StructStat {
+    /** Device ID of device containing file. */
+    public final long st_dev; /*dev_t*/
+
+    /** File serial number (inode). */
+    public final long st_ino; /*ino_t*/
+
+    /** Mode (permissions) of file. */
+    public final int st_mode; /*mode_t*/
+
+    /** Number of hard links to the file. */
+    public final long st_nlink; /*nlink_t*/
+
+    /** User ID of file. */
+    public final int st_uid; /*uid_t*/
+
+    /** Group ID of file. */
+    public final int st_gid; /*gid_t*/
+
+    /** Device ID (if file is character or block special). */
+    public final long st_rdev; /*dev_t*/
+
+    /**
+     * For regular files, the file size in bytes.
+     * For symbolic links, the length in bytes of the pathname contained in the symbolic link.
+     * For a shared memory object, the length in bytes.
+     * For a typed memory object, the length in bytes.
+     * For other file types, the use of this field is unspecified.
+     */
+    public final long st_size; /*off_t*/
+
+    /** Seconds part of time of last access. */
+    public final long st_atime; /*time_t*/
+
+    /** StructTimespec with time of last access. */
+    public final StructTimespec st_atim;
+
+    /** Seconds part of time of last data modification. */
+    public final long st_mtime; /*time_t*/
+
+    /** StructTimespec with time of last modification. */
+    public final StructTimespec st_mtim;
+
+    /** Seconds part of time of last status change */
+    public final long st_ctime; /*time_t*/
+
+    /** StructTimespec with time of last status change. */
+    public final StructTimespec st_ctim;
+
+    /**
+     * A file system-specific preferred I/O block size for this object.
+     * For some file system types, this may vary from file to file.
+     */
+    public final long st_blksize; /*blksize_t*/
+
+    /** Number of blocks allocated for this object. */
+    public final long st_blocks; /*blkcnt_t*/
+
+    /**
+     * Constructs an instance with the given field values.
+     */
+    public StructStat(long st_dev, long st_ino, int st_mode, long st_nlink, int st_uid, int st_gid,
+            long st_rdev, long st_size, long st_atime, long st_mtime, long st_ctime,
+            long st_blksize, long st_blocks) {
+        this(st_dev, st_ino, st_mode, st_nlink, st_uid, st_gid,
+                st_rdev, st_size, new StructTimespec(st_atime, 0L), new StructTimespec(st_mtime, 0L),
+                new StructTimespec(st_ctime, 0L), st_blksize, st_blocks);
+    }
+
+    /**
+     * Constructs an instance with the given field values.
+     */
+    public StructStat(long st_dev, long st_ino, int st_mode, long st_nlink, int st_uid, int st_gid,
+            long st_rdev, long st_size, StructTimespec st_atim, StructTimespec st_mtim,
+            StructTimespec st_ctim, long st_blksize, long st_blocks) {
+        this.st_dev = st_dev;
+        this.st_ino = st_ino;
+        this.st_mode = st_mode;
+        this.st_nlink = st_nlink;
+        this.st_uid = st_uid;
+        this.st_gid = st_gid;
+        this.st_rdev = st_rdev;
+        this.st_size = st_size;
+        this.st_atime = st_atim.tv_sec;
+        this.st_mtime = st_mtim.tv_sec;
+        this.st_ctime = st_ctim.tv_sec;
+        this.st_atim = st_atim;
+        this.st_mtim = st_mtim;
+        this.st_ctim = st_ctim;
+        this.st_blksize = st_blksize;
+        this.st_blocks = st_blocks;
+    }
+
+    @Override public String toString() {
+        return Objects.toString(this);
+    }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/StructTimespec.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/StructTimespec.java
new file mode 100644
index 0000000..c106780
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/StructTimespec.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 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 android.system;
+
+import libcore.util.Objects;;
+
+/**
+ * Corresponds to C's {@code struct timespec} from {@code <time.h>}.
+ */
+public final class StructTimespec implements Comparable<StructTimespec> {
+    /** Seconds part of time of last data modification. */
+    public final long tv_sec; /*time_t*/
+
+    /** Nanoseconds (values are [0, 999999999]). */
+    public final long tv_nsec;
+
+    public StructTimespec(long tv_sec, long tv_nsec) {
+        this.tv_sec = tv_sec;
+        this.tv_nsec = tv_nsec;
+        if (tv_nsec < 0 || tv_nsec > 999_999_999) {
+            throw new IllegalArgumentException(
+                    "tv_nsec value " + tv_nsec + " is not in [0, 999999999]");
+        }
+    }
+
+    @Override
+    public int compareTo(StructTimespec other) {
+        if (tv_sec > other.tv_sec) {
+            return 1;
+        }
+        if (tv_sec < other.tv_sec) {
+            return -1;
+        }
+        if (tv_nsec > other.tv_nsec) {
+            return 1;
+        }
+        if (tv_nsec < other.tv_nsec) {
+            return -1;
+        }
+        return 0;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        StructTimespec that = (StructTimespec) o;
+
+        if (tv_sec != that.tv_sec) return false;
+        return tv_nsec == that.tv_nsec;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = (int) (tv_sec ^ (tv_sec >>> 32));
+        result = 31 * result + (int) (tv_nsec ^ (tv_nsec >>> 32));
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return Objects.toString(this);
+    }
+}
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java
similarity index 65%
rename from ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodRuntimeNative.java
rename to ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java
index 6540221..e9b305e 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodRuntimeNative.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java
@@ -15,6 +15,9 @@
  */
 package com.android.ravenwood.common;
 
+import android.system.ErrnoException;
+import android.system.StructStat;
+
 import java.io.FileDescriptor;
 
 /**
@@ -31,19 +34,25 @@
 
     public static native void applyFreeFunction(long freeFunction, long nativePtr);
 
-    public static native long nLseek(int fd, long offset, int whence);
+    private static native long nLseek(int fd, long offset, int whence) throws ErrnoException;
 
-    public static native int[] nPipe2(int flags);
+    private static native int[] nPipe2(int flags) throws ErrnoException;
 
-    public static native int nDup(int oldfd);
+    private static native int nDup(int oldfd) throws ErrnoException;
 
-    public static native int nFcntlInt(int fd, int cmd, int arg);
+    private static native int nFcntlInt(int fd, int cmd, int arg) throws ErrnoException;
 
-    public static long lseek(FileDescriptor fd, long offset, int whence) {
+    private static native StructStat nFstat(int fd) throws ErrnoException;
+
+    public static native StructStat lstat(String path) throws ErrnoException;
+
+    public static native StructStat stat(String path) throws ErrnoException;
+
+    public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException {
         return nLseek(JvmWorkaround.getInstance().getFdInt(fd), offset, whence);
     }
 
-    public static FileDescriptor[] pipe2(int flags) {
+    public static FileDescriptor[] pipe2(int flags) throws ErrnoException {
         var fds = nPipe2(flags);
         var ret = new FileDescriptor[] {
                 new FileDescriptor(),
@@ -55,7 +64,7 @@
         return ret;
     }
 
-    public static FileDescriptor dup(FileDescriptor fd) {
+    public static FileDescriptor dup(FileDescriptor fd) throws ErrnoException {
         var fdInt = nDup(JvmWorkaround.getInstance().getFdInt(fd));
 
         var retFd = new java.io.FileDescriptor();
@@ -63,9 +72,15 @@
         return retFd;
     }
 
-    public static int fcntlInt(FileDescriptor fd, int cmd, int arg) {
+    public static int fcntlInt(FileDescriptor fd, int cmd, int arg) throws ErrnoException {
         var fdInt = JvmWorkaround.getInstance().getFdInt(fd);
 
         return nFcntlInt(fdInt, cmd, arg);
     }
+
+    public static StructStat fstat(FileDescriptor fd) throws ErrnoException {
+        var fdInt = JvmWorkaround.getInstance().getFdInt(fd);
+
+        return nFstat(fdInt);
+    }
 }
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Objects.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Objects.java
new file mode 100644
index 0000000..3781fcf
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Objects.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+
+public final class Objects {
+    private Objects() {}
+
+    /**
+     * Returns a string reporting the value of each declared field, via reflection.
+     * Static and transient fields are automatically skipped. Produces output like
+     * "SimpleClassName[integer=1234,string="hello",character='c',intArray=[1,2,3]]".
+     */
+    public static String toString(Object o) {
+        Class<?> c = o.getClass();
+        StringBuilder sb = new StringBuilder();
+        sb.append(c.getSimpleName()).append('[');
+        int i = 0;
+        for (Field f : c.getDeclaredFields()) {
+            if ((f.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) != 0) {
+                continue;
+            }
+            f.setAccessible(true);
+            try {
+                Object value = f.get(o);
+
+                if (i++ > 0) {
+                    sb.append(',');
+                }
+
+                sb.append(f.getName());
+                sb.append('=');
+
+                if (value.getClass().isArray()) {
+                    if (value.getClass() == boolean[].class) {
+                        sb.append(Arrays.toString((boolean[]) value));
+                    } else if (value.getClass() == byte[].class) {
+                        sb.append(Arrays.toString((byte[]) value));
+                    } else if (value.getClass() == char[].class) {
+                        sb.append(Arrays.toString((char[]) value));
+                    } else if (value.getClass() == double[].class) {
+                        sb.append(Arrays.toString((double[]) value));
+                    } else if (value.getClass() == float[].class) {
+                        sb.append(Arrays.toString((float[]) value));
+                    } else if (value.getClass() == int[].class) {
+                        sb.append(Arrays.toString((int[]) value));
+                    } else if (value.getClass() == long[].class) {
+                        sb.append(Arrays.toString((long[]) value));
+                    } else if (value.getClass() == short[].class) {
+                        sb.append(Arrays.toString((short[]) value));
+                    } else {
+                        sb.append(Arrays.toString((Object[]) value));
+                    }
+                } else if (value.getClass() == Character.class) {
+                    sb.append('\'').append(value).append('\'');
+                } else if (value.getClass() == String.class) {
+                    sb.append('"').append(value).append('"');
+                } else {
+                    sb.append(value);
+                }
+            } catch (IllegalAccessException unexpected) {
+                throw new AssertionError(unexpected);
+            }
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+}
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
index 34cf9f9..e0a3e1c 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -19,6 +19,9 @@
 #include <string.h>
 #include <unistd.h>
 #include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedUtfChars.h>
+
 #include "jni.h"
 #include "utils/Log.h"
 #include "utils/misc.h"
@@ -41,6 +44,75 @@
     return rc;
 }
 
+// ---- Helper functions ---
+
+static jclass g_StructStat;
+static jclass g_StructTimespecClass;
+
+static jclass findClass(JNIEnv* env, const char* name) {
+    ScopedLocalRef<jclass> localClass(env, env->FindClass(name));
+    jclass result = reinterpret_cast<jclass>(env->NewGlobalRef(localClass.get()));
+    if (result == NULL) {
+        ALOGE("failed to find class '%s'", name);
+        abort();
+    }
+    return result;
+}
+
+static jobject makeStructTimespec(JNIEnv* env, const struct timespec& ts) {
+    static jmethodID ctor = env->GetMethodID(g_StructTimespecClass, "<init>",
+            "(JJ)V");
+    if (ctor == NULL) {
+        return NULL;
+    }
+    return env->NewObject(g_StructTimespecClass, ctor,
+            static_cast<jlong>(ts.tv_sec), static_cast<jlong>(ts.tv_nsec));
+}
+
+static jobject makeStructStat(JNIEnv* env, const struct stat64& sb) {
+    static jmethodID ctor = env->GetMethodID(g_StructStat, "<init>",
+            "(JJIJIIJJLandroid/system/StructTimespec;Landroid/system/StructTimespec;Landroid/system/StructTimespec;JJ)V");
+    if (ctor == NULL) {
+        return NULL;
+    }
+
+    jobject atim_timespec = makeStructTimespec(env, sb.st_atim);
+    if (atim_timespec == NULL) {
+        return NULL;
+    }
+    jobject mtim_timespec = makeStructTimespec(env, sb.st_mtim);
+    if (mtim_timespec == NULL) {
+        return NULL;
+    }
+    jobject ctim_timespec = makeStructTimespec(env, sb.st_ctim);
+    if (ctim_timespec == NULL) {
+        return NULL;
+    }
+
+    return env->NewObject(g_StructStat, ctor,
+            static_cast<jlong>(sb.st_dev), static_cast<jlong>(sb.st_ino),
+            static_cast<jint>(sb.st_mode), static_cast<jlong>(sb.st_nlink),
+            static_cast<jint>(sb.st_uid), static_cast<jint>(sb.st_gid),
+            static_cast<jlong>(sb.st_rdev), static_cast<jlong>(sb.st_size),
+            atim_timespec, mtim_timespec, ctim_timespec,
+            static_cast<jlong>(sb.st_blksize), static_cast<jlong>(sb.st_blocks));
+}
+
+static jobject doStat(JNIEnv* env, jstring javaPath, bool isLstat) {
+    ScopedUtfChars path(env, javaPath);
+    if (path.c_str() == NULL) {
+        return NULL;
+    }
+    struct stat64 sb;
+    int rc = isLstat ? TEMP_FAILURE_RETRY(lstat64(path.c_str(), &sb))
+                     : TEMP_FAILURE_RETRY(stat64(path.c_str(), &sb));
+    if (rc == -1) {
+        throwErrnoException(env, isLstat ? "lstat" : "stat");
+        return NULL;
+    }
+    return makeStructStat(env, sb);
+}
+
 // ---- JNI methods ----
 
 typedef void (*FreeFunction)(void*);
@@ -77,6 +149,24 @@
     return throwIfMinusOne(env, "fcntl", TEMP_FAILURE_RETRY(fcntl(fd, F_DUPFD_CLOEXEC, 0)));
 }
 
+static jobject nFstat(JNIEnv* env, jobject, jint fd) {
+    struct stat64 sb;
+    int rc = TEMP_FAILURE_RETRY(fstat64(fd, &sb));
+    if (rc == -1) {
+        throwErrnoException(env, "fstat");
+        return NULL;
+    }
+    return makeStructStat(env, sb);
+}
+
+static jobject Linux_lstat(JNIEnv* env, jobject, jstring javaPath) {
+    return doStat(env, javaPath, true);
+}
+
+static jobject Linux_stat(JNIEnv* env, jobject, jstring javaPath) {
+    return doStat(env, javaPath, false);
+}
+
 // ---- Registration ----
 
 static const JNINativeMethod sMethods[] =
@@ -86,6 +176,9 @@
     { "nLseek", "(IJI)J", (void*)nLseek },
     { "nPipe2", "(I)[I", (void*)nPipe2 },
     { "nDup", "(I)I", (void*)nDup },
+    { "nFstat", "(I)Landroid/system/StructStat;", (void*)nFstat },
+    { "lstat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_lstat },
+    { "stat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_stat },
 };
 
 extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
@@ -101,6 +194,9 @@
 
     ALOGI("%s: JNI_OnLoad", __FILE__);
 
+    g_StructStat = findClass(env, "android/system/StructStat");
+    g_StructTimespecClass = findClass(env, "android/system/StructTimespec");
+
     jint res = jniRegisterNativeMethods(env, "com/android/ravenwood/common/RavenwoodRuntimeNative",
             sMethods, NELEM(sMethods));
     if (res < 0) {
diff --git a/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java b/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java
index b5038e6..05275b2 100644
--- a/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java
+++ b/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java
@@ -15,15 +15,26 @@
  */
 package com.android.ravenwood.runtimetest;
 
+import static android.system.OsConstants.S_ISBLK;
+import static android.system.OsConstants.S_ISCHR;
+import static android.system.OsConstants.S_ISDIR;
+import static android.system.OsConstants.S_ISFIFO;
+import static android.system.OsConstants.S_ISLNK;
+import static android.system.OsConstants.S_ISREG;
+import static android.system.OsConstants.S_ISSOCK;
+
 import static org.junit.Assert.assertEquals;
 
+import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
+
 import android.system.Os;
 import android.system.OsConstants;
+import android.system.StructStat;
+import android.system.StructTimespec;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.ravenwood.common.JvmWorkaround;
-import com.android.ravenwood.common.RavenwoodRuntimeNative;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -32,8 +43,15 @@
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
-import java.io.IOException;
 import java.io.RandomAccessFile;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
 public class OsTest {
@@ -41,7 +59,7 @@
         void accept(T var1) throws Exception;
     }
 
-    private void withTestFile(ConsumerWithThrow<FileDescriptor> consumer) throws Exception {
+    private void withTestFileFD(ConsumerWithThrow<FileDescriptor> consumer) throws Exception {
         File file = File.createTempFile("osTest", "bin");
         try (var raf = new RandomAccessFile(file, "rw")) {
             var fd = raf.getFD();
@@ -57,9 +75,20 @@
         }
     }
 
+    private void withTestFile(ConsumerWithThrow<Path> consumer) throws Exception {
+        var path = Files.createTempFile("osTest", "bin");
+        try (var os = Files.newOutputStream(path)) {
+            os.write(1);
+            os.write(2);
+            os.write(3);
+            os.write(4);
+        }
+        consumer.accept(path);
+    }
+
     @Test
     public void testLseek() throws Exception {
-        withTestFile((fd) -> {
+        withTestFileFD((fd) -> {
             assertEquals(4, Os.lseek(fd, 4, OsConstants.SEEK_SET));
             assertEquals(4, Os.lseek(fd, 0, OsConstants.SEEK_CUR));
             assertEquals(6, Os.lseek(fd, 2, OsConstants.SEEK_CUR));
@@ -68,7 +97,7 @@
 
     @Test
     public void testDup() throws Exception {
-        withTestFile((fd) -> {
+        withTestFileFD((fd) -> {
             var dup = Os.dup(fd);
 
             checkAreDup(fd, dup);
@@ -85,7 +114,7 @@
 
     @Test
     public void testFcntlInt() throws Exception {
-        withTestFile((fd) -> {
+        withTestFileFD((fd) -> {
             var dupInt = Os.fcntlInt(fd, 0, 0);
 
             var dup = new FileDescriptor();
@@ -95,16 +124,90 @@
         });
     }
 
-    private static void write(FileDescriptor fd, int oneByte)  throws IOException {
+    @Test
+    public void testStat() throws Exception {
+        withTestFile(path -> {
+            var attr = Files.readAttributes(path, PosixFileAttributes.class);
+            var stat = Os.stat(path.toAbsolutePath().toString());
+            assertAttributesEqual(attr, stat);
+        });
+    }
+
+    @Test
+    public void testLstat() throws Exception {
+        withTestFile(path -> {
+            // Create a symbolic link
+            var lnk = Files.createTempFile("osTest", "lnk");
+            Files.delete(lnk);
+            Files.createSymbolicLink(lnk, path);
+
+            // Test lstat
+            var attr = Files.readAttributes(lnk, PosixFileAttributes.class, NOFOLLOW_LINKS);
+            var stat = Os.lstat(lnk.toAbsolutePath().toString());
+            assertAttributesEqual(attr, stat);
+
+            // Test stat
+            var followAttr = Files.readAttributes(lnk, PosixFileAttributes.class);
+            var followStat = Os.stat(lnk.toAbsolutePath().toString());
+            assertAttributesEqual(followAttr, followStat);
+        });
+    }
+
+    @Test
+    public void testFstat() throws Exception {
+        withTestFile(path -> {
+            var attr = Files.readAttributes(path, PosixFileAttributes.class);
+            try (var raf = new RandomAccessFile(path.toFile(), "r")) {
+                var fd = raf.getFD();
+                var stat = Os.fstat(fd);
+                assertAttributesEqual(attr, stat);
+            }
+        });
+    }
+
+    // Verify StructStat values from libcore against native JVM PosixFileAttributes
+    private static void assertAttributesEqual(PosixFileAttributes attr, StructStat stat) {
+        assertEquals(attr.lastModifiedTime(), convertTimespecToFileTime(stat.st_mtim));
+        assertEquals(attr.size(), stat.st_size);
+        assertEquals(attr.isDirectory(), S_ISDIR(stat.st_mode));
+        assertEquals(attr.isRegularFile(), S_ISREG(stat.st_mode));
+        assertEquals(attr.isSymbolicLink(), S_ISLNK(stat.st_mode));
+        assertEquals(attr.isOther(), S_ISCHR(stat.st_mode)
+                || S_ISBLK(stat.st_mode) || S_ISFIFO(stat.st_mode) || S_ISSOCK(stat.st_mode));
+        assertEquals(attr.permissions(), convertModeToPosixPerms(stat.st_mode));
+
+    }
+
+    private static FileTime convertTimespecToFileTime(StructTimespec ts) {
+        var nanos = TimeUnit.SECONDS.toNanos(ts.tv_sec);
+        nanos += ts.tv_nsec;
+        return FileTime.from(nanos, TimeUnit.NANOSECONDS);
+    }
+
+    private static Set<PosixFilePermission> convertModeToPosixPerms(int mode) {
+        var set = new HashSet<PosixFilePermission>();
+        if ((mode & OsConstants.S_IRUSR) != 0) set.add(PosixFilePermission.OWNER_READ);
+        if ((mode & OsConstants.S_IWUSR) != 0) set.add(PosixFilePermission.OWNER_WRITE);
+        if ((mode & OsConstants.S_IXUSR) != 0) set.add(PosixFilePermission.OWNER_EXECUTE);
+        if ((mode & OsConstants.S_IRGRP) != 0) set.add(PosixFilePermission.GROUP_READ);
+        if ((mode & OsConstants.S_IWGRP) != 0) set.add(PosixFilePermission.GROUP_WRITE);
+        if ((mode & OsConstants.S_IXGRP) != 0) set.add(PosixFilePermission.GROUP_EXECUTE);
+        if ((mode & OsConstants.S_IROTH) != 0) set.add(PosixFilePermission.OTHERS_READ);
+        if ((mode & OsConstants.S_IWOTH) != 0) set.add(PosixFilePermission.OTHERS_WRITE);
+        if ((mode & OsConstants.S_IXOTH) != 0) set.add(PosixFilePermission.OTHERS_EXECUTE);
+        return set;
+    }
+
+    private static void write(FileDescriptor fd, int oneByte) throws Exception {
         // Create a dup to avoid closing the FD.
-        try (var dup = new FileOutputStream(RavenwoodRuntimeNative.dup(fd))) {
+        try (var dup = new FileOutputStream(Os.dup(fd))) {
             dup.write(oneByte);
         }
     }
 
-    private static int read(FileDescriptor fd) throws IOException {
+    private static int read(FileDescriptor fd) throws Exception {
         // Create a dup to avoid closing the FD.
-        try (var dup = new FileInputStream(RavenwoodRuntimeNative.dup(fd))) {
+        try (var dup = new FileInputStream(Os.dup(fd))) {
             return dup.read();
         }
     }
diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp
index 7a99b60..311addb 100644
--- a/services/accessibility/Android.bp
+++ b/services/accessibility/Android.bp
@@ -29,10 +29,12 @@
         "//frameworks/base/packages/SettingsLib/RestrictedLockUtils:SettingsLibRestrictedLockUtilsSrc",
     ],
     libs: [
+        "aatf",
         "services.core",
         "androidx.annotation_annotation",
     ],
     static_libs: [
+        "a11ychecker-protos-java-proto-lite",
         "com_android_server_accessibility_flags_lib",
         "//frameworks/base/packages/SystemUI/aconfig:com_android_systemui_flags_lib",
 
@@ -68,3 +70,14 @@
     name: "com_android_server_accessibility_flags_lib",
     aconfig_declarations: "com_android_server_accessibility_flags",
 }
+
+java_library_static {
+    name: "a11ychecker-protos-java-proto-lite",
+    proto: {
+        type: "lite",
+        canonical_path_from_root: false,
+    },
+    srcs: [
+        "java/**/a11ychecker/proto/*.proto",
+    ],
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
new file mode 100644
index 0000000..55af9a0
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 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.accessibility.a11ychecker;
+
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+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;
+import com.android.server.accessibility.a11ychecker.A11yCheckerProto.AccessibilityCheckClass;
+import com.android.server.accessibility.a11ychecker.A11yCheckerProto.AccessibilityCheckResultReported;
+import com.android.server.accessibility.a11ychecker.A11yCheckerProto.AccessibilityCheckResultType;
+
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult;
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheck;
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheckResult;
+import com.google.android.apps.common.testing.accessibility.framework.checks.ClassNameCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.ClickableSpanCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.DuplicateClickableBoundsCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.DuplicateSpeakableTextCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.EditableContentDescCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.ImageContrastCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.LinkPurposeUnclearCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.RedundantDescriptionCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.SpeakableTextPresentCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.TextContrastCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.TextSizeCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.TouchTargetSizeCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.TraversalOrderCheck;
+
+import java.util.AbstractMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Util class to process a11y checker results for logging.
+ *
+ * @hide
+ */
+public class AccessibilityCheckerUtils {
+
+    private static final String LOG_TAG = "AccessibilityCheckerUtils";
+    @VisibleForTesting
+    // LINT.IfChange
+    static final Map<Class<? extends AccessibilityHierarchyCheck>, AccessibilityCheckClass>
+            CHECK_CLASS_TO_ENUM_MAP =
+            Map.ofEntries(
+                    classMapEntry(ClassNameCheck.class, AccessibilityCheckClass.CLASS_NAME_CHECK),
+                    classMapEntry(ClickableSpanCheck.class,
+                            AccessibilityCheckClass.CLICKABLE_SPAN_CHECK),
+                    classMapEntry(DuplicateClickableBoundsCheck.class,
+                            AccessibilityCheckClass.DUPLICATE_CLICKABLE_BOUNDS_CHECK),
+                    classMapEntry(DuplicateSpeakableTextCheck.class,
+                            AccessibilityCheckClass.DUPLICATE_SPEAKABLE_TEXT_CHECK),
+                    classMapEntry(EditableContentDescCheck.class,
+                            AccessibilityCheckClass.EDITABLE_CONTENT_DESC_CHECK),
+                    classMapEntry(ImageContrastCheck.class,
+                            AccessibilityCheckClass.IMAGE_CONTRAST_CHECK),
+                    classMapEntry(LinkPurposeUnclearCheck.class,
+                            AccessibilityCheckClass.LINK_PURPOSE_UNCLEAR_CHECK),
+                    classMapEntry(RedundantDescriptionCheck.class,
+                            AccessibilityCheckClass.REDUNDANT_DESCRIPTION_CHECK),
+                    classMapEntry(SpeakableTextPresentCheck.class,
+                            AccessibilityCheckClass.SPEAKABLE_TEXT_PRESENT_CHECK),
+                    classMapEntry(TextContrastCheck.class,
+                            AccessibilityCheckClass.TEXT_CONTRAST_CHECK),
+                    classMapEntry(TextSizeCheck.class, AccessibilityCheckClass.TEXT_SIZE_CHECK),
+                    classMapEntry(TouchTargetSizeCheck.class,
+                            AccessibilityCheckClass.TOUCH_TARGET_SIZE_CHECK),
+                    classMapEntry(TraversalOrderCheck.class,
+                            AccessibilityCheckClass.TRAVERSAL_ORDER_CHECK));
+    // LINT.ThenChange(/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto)
+
+    static Set<AccessibilityCheckResultReported> processResults(
+            Context context,
+            AccessibilityNodeInfo nodeInfo,
+            List<AccessibilityHierarchyCheckResult> checkResults,
+            @Nullable AccessibilityEvent accessibilityEvent,
+            ComponentName a11yServiceComponentName) {
+        return processResults(nodeInfo, checkResults, accessibilityEvent,
+                context.getPackageManager(), a11yServiceComponentName);
+    }
+
+    @VisibleForTesting
+    static Set<AccessibilityCheckResultReported> processResults(
+            AccessibilityNodeInfo nodeInfo,
+            List<AccessibilityHierarchyCheckResult> checkResults,
+            @Nullable AccessibilityEvent accessibilityEvent,
+            PackageManager packageManager,
+            ComponentName a11yServiceComponentName) {
+        String appPackageName = nodeInfo.getPackageName().toString();
+        AccessibilityCheckResultReported.Builder builder;
+        try {
+            builder = AccessibilityCheckResultReported.newBuilder()
+                    .setPackageName(appPackageName)
+                    .setAppVersionCode(getAppVersionCode(packageManager, appPackageName))
+                    .setUiElementPath(AccessibilityNodePathBuilder.createNodePath(nodeInfo))
+                    .setActivityName(getActivityName(packageManager, accessibilityEvent))
+                    .setWindowTitle(getWindowTitle(nodeInfo))
+                    .setSourceComponentName(a11yServiceComponentName.flattenToString())
+                    .setSourceVersionCode(
+                            getAppVersionCode(packageManager,
+                                    a11yServiceComponentName.getPackageName()));
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.e(LOG_TAG, "Unknown package name", e);
+            return Set.of();
+        }
+
+        return checkResults.stream()
+                .filter(checkResult -> checkResult.getType()
+                        == AccessibilityCheckResult.AccessibilityCheckResultType.ERROR
+                        || checkResult.getType()
+                        == AccessibilityCheckResult.AccessibilityCheckResultType.WARNING)
+                .map(checkResult -> builder.setResultCheckClass(
+                        getCheckClass(checkResult)).setResultType(
+                        getCheckResultType(checkResult)).setResultId(
+                        checkResult.getResultId()).build())
+                .collect(Collectors.toUnmodifiableSet());
+    }
+
+    private static long getAppVersionCode(PackageManager packageManager, String packageName) throws
+            PackageManager.NameNotFoundException {
+        PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
+        return packageInfo.getLongVersionCode();
+    }
+
+    /**
+     * Returns the simple class name of the Activity providing the cache update, if available,
+     * or an empty String if not.
+     */
+    @VisibleForTesting
+    static String getActivityName(
+            PackageManager packageManager, @Nullable AccessibilityEvent accessibilityEvent) {
+        if (accessibilityEvent == 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.
+            }
+        }
+        return "";
+    }
+
+    /**
+     * Returns the title of the window containing the a11y node.
+     */
+    private static String getWindowTitle(AccessibilityNodeInfo nodeInfo) {
+        if (nodeInfo.getWindow() == null) {
+            return "";
+        }
+        CharSequence windowTitle = nodeInfo.getWindow().getTitle();
+        return windowTitle == null ? "" : windowTitle.toString();
+    }
+
+    /**
+     * Maps the {@link AccessibilityHierarchyCheck} class that produced the given result, with the
+     * corresponding {@link AccessibilityCheckClass} enum. This enumeration is to avoid relying on
+     * String class names in the logging, which can be proguarded. It also reduces the logging size.
+     */
+    private static AccessibilityCheckClass getCheckClass(
+            AccessibilityHierarchyCheckResult checkResult) {
+        if (CHECK_CLASS_TO_ENUM_MAP.containsKey(checkResult.getSourceCheckClass())) {
+            return CHECK_CLASS_TO_ENUM_MAP.get(checkResult.getSourceCheckClass());
+        }
+        return AccessibilityCheckClass.UNKNOWN_CHECK;
+    }
+
+    private static AccessibilityCheckResultType getCheckResultType(
+            AccessibilityHierarchyCheckResult checkResult) {
+        return switch (checkResult.getType()) {
+            case ERROR -> AccessibilityCheckResultType.ERROR;
+            case WARNING -> AccessibilityCheckResultType.WARNING;
+            default -> AccessibilityCheckResultType.UNKNOWN_RESULT_TYPE;
+        };
+    }
+
+    private static Map.Entry<Class<? extends AccessibilityHierarchyCheck>,
+            AccessibilityCheckClass> classMapEntry(
+            Class<? extends AccessibilityHierarchyCheck> checkClass,
+            AccessibilityCheckClass checkClassEnum) {
+        return new AbstractMap.SimpleImmutableEntry<>(checkClass, checkClassEnum);
+    }
+}
diff --git a/core/java/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilder.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilder.java
similarity index 98%
rename from core/java/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilder.java
rename to services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilder.java
index 2996dde..bbfb217 100644
--- a/core/java/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilder.java
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilder.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.view.accessibility.a11ychecker;
+package com.android.server.accessibility.a11ychecker;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/core/java/android/view/accessibility/a11ychecker/OWNERS b/services/accessibility/java/com/android/server/accessibility/a11ychecker/OWNERS
similarity index 100%
rename from core/java/android/view/accessibility/a11ychecker/OWNERS
rename to services/accessibility/java/com/android/server/accessibility/a11ychecker/OWNERS
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto b/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto
new file mode 100644
index 0000000..8beed4a
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+syntax = "proto2";
+package android.accessibility;
+
+option java_package = "com.android.server.accessibility.a11ychecker";
+option java_outer_classname = "A11yCheckerProto";
+
+// TODO(b/326385939): remove and replace usage with the atom extension proto, when submitted.
+/** Logs the result of an AccessibilityCheck. */
+message AccessibilityCheckResultReported {
+  // Package name of the app containing the checked View.
+  optional string package_name = 1;
+  // Version code of the app containing the checked View.
+  optional int64 app_version_code = 2;
+  // The path of the View starting from the root element in the window. Each element is
+  // represented by the View's resource id, when available, or the View's class name.
+  optional string ui_element_path = 3;
+  // Class name of the activity containing the checked View.
+  optional string activity_name = 4;
+  // Title of the window containing the checked View.
+  optional string window_title = 5;
+  // The flattened component name of the app running the AccessibilityService which provided the a11y node.
+  optional string source_component_name = 6;
+  // Version code of the app running the AccessibilityService that provided the a11y node.
+  optional int64 source_version_code = 7;
+  // Class Name of the AccessibilityCheck that produced the result.
+  optional AccessibilityCheckClass result_check_class = 8;
+  // Result type of the AccessibilityCheckResult.
+  optional AccessibilityCheckResultType result_type = 9;
+  // Result ID of the AccessibilityCheckResult.
+  optional int32 result_id = 10;
+}
+
+/** The AccessibilityCheck class. */
+// LINT.IfChange
+enum AccessibilityCheckClass {
+  UNKNOWN_CHECK = 0;
+  CLASS_NAME_CHECK = 1;
+  CLICKABLE_SPAN_CHECK = 2;
+  DUPLICATE_CLICKABLE_BOUNDS_CHECK = 3;
+  DUPLICATE_SPEAKABLE_TEXT_CHECK = 4;
+  EDITABLE_CONTENT_DESC_CHECK = 5;
+  IMAGE_CONTRAST_CHECK = 6;
+  LINK_PURPOSE_UNCLEAR_CHECK = 7;
+  REDUNDANT_DESCRIPTION_CHECK = 8;
+  SPEAKABLE_TEXT_PRESENT_CHECK = 9;
+  TEXT_CONTRAST_CHECK = 10;
+  TEXT_SIZE_CHECK = 11;
+  TOUCH_TARGET_SIZE_CHECK = 12;
+  TRAVERSAL_ORDER_CHECK = 13;
+}
+// LINT.ThenChange(/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java)
+
+/** The type of AccessibilityCheckResult */
+enum AccessibilityCheckResultType {
+  UNKNOWN_RESULT_TYPE = 0;
+  ERROR = 1;
+  WARNING = 2;
+}
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index d7da2f0..026c69c 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -846,7 +846,6 @@
                 mCallingAppUid,
                 event.mIsCredentialRequest,
                 event.mWebviewRequestedCredential,
-                event.mFilteredFillabaleViewCount,
                 event.mViewFillableTotalCount,
                 event.mViewFillFailureCount,
                 event.mFocusedId,
@@ -859,7 +858,8 @@
                 event.mSuggestionPresentedLastTimestampMs,
                 event.mFocusedVirtualAutofillId,
                 event.mFieldFirstLength,
-                event.mFieldLastLength);
+                event.mFieldLastLength,
+                event.mFilteredFillabaleViewCount);
         mEventInternal = Optional.empty();
     }
 
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 458749d..2d91331 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -185,6 +185,12 @@
 
     final Context mContext;
 
+    private static final int[] INTERESTING_APP_OPS = new int[] {
+        AppOpsManager.OP_GET_ACCOUNTS,
+        AppOpsManager.OP_READ_CONTACTS,
+        AppOpsManager.OP_WRITE_CONTACTS,
+    };
+
     private final PackageManager mPackageManager;
     private final AppOpsManager mAppOpsManager;
     private UserManager mUserManager;
@@ -388,74 +394,47 @@
         }.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
 
         // Cancel account request notification if an app op was preventing the account access
-        mAppOpsManager.startWatchingMode(AppOpsManager.OP_GET_ACCOUNTS, null,
-                new AppOpsManager.OnOpChangedInternalListener() {
-            @Override
-            public void onOpChanged(int op, String packageName) {
-                try {
-                    final int userId = ActivityManager.getCurrentUser();
-                    final int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
-                    final int mode = mAppOpsManager.checkOpNoThrow(
-                            AppOpsManager.OP_GET_ACCOUNTS, uid, packageName);
-                    if (mode == AppOpsManager.MODE_ALLOWED) {
-                        final long identity = Binder.clearCallingIdentity();
-                        try {
-                            UserAccounts accounts = getUserAccounts(userId);
-                            cancelAccountAccessRequestNotificationIfNeeded(
-                                    packageName, uid, true, accounts);
-                        } finally {
-                            Binder.restoreCallingIdentity(identity);
-                        }
-                    }
-                } catch (NameNotFoundException e) {
-                    /* ignore */
-                } catch (SQLiteCantOpenDatabaseException e) {
-                    Log.w(TAG, "Can't read accounts database", e);
-                    return;
-                }
-            }
-        });
+        for (int i = 0; i < INTERESTING_APP_OPS.length; ++i) {
+            mAppOpsManager.startWatchingMode(INTERESTING_APP_OPS[i], null,
+                    new OnInterestingAppOpChangedListener());
+        }
 
-        // Cancel account request notification if a permission was preventing the account access
-        mPackageManager.addOnPermissionsChangeListener(
-                (int uid) -> {
-            // Permission changes cause requires updating accounts cache.
+        // Clear the accounts cache on permission changes.
+        // The specific permissions we care about are backed by AppOps, so just
+        // let the change events on those handle clearing any notifications.
+        mPackageManager.addOnPermissionsChangeListener((int uid) -> {
             AccountManager.invalidateLocalAccountsDataCaches();
-
-            Account[] accounts = null;
-            String[] packageNames = mPackageManager.getPackagesForUid(uid);
-            if (packageNames != null) {
-                final int userId = UserHandle.getUserId(uid);
-                final long identity = Binder.clearCallingIdentity();
-                try {
-                    for (String packageName : packageNames) {
-                                // if app asked for permission we need to cancel notification even
-                                // for O+ applications.
-                                if (mPackageManager.checkPermission(
-                                        Manifest.permission.GET_ACCOUNTS,
-                                        packageName) != PackageManager.PERMISSION_GRANTED) {
-                                    continue;
-                                }
-
-                        if (accounts == null) {
-                            accounts = getAccountsOrEmptyArray(null, userId, "android");
-                            if (ArrayUtils.isEmpty(accounts)) {
-                                return;
-                            }
-                        }
-                        UserAccounts userAccounts = getUserAccounts(UserHandle.getUserId(uid));
-                        for (Account account : accounts) {
-                            cancelAccountAccessRequestNotificationIfNeeded(
-                                    account, uid, packageName, true, userAccounts);
-                        }
-                    }
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
-            }
         });
     }
 
+    private class OnInterestingAppOpChangedListener implements AppOpsManager.OnOpChangedListener {
+        @Override
+        public void onOpChanged(String op, String packageName) {
+            final int userId = ActivityManager.getCurrentUser();
+            final int packageUid;
+            try {
+                packageUid = mPackageManager.getPackageUidAsUser(packageName, userId);
+            } catch (NameNotFoundException e) {
+                /* ignore */
+                return;
+            }
+
+            final int mode = mAppOpsManager.checkOpNoThrow(op, packageUid, packageName);
+            if (mode != AppOpsManager.MODE_ALLOWED) {
+                return;
+            }
+
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                cancelAccountAccessRequestNotificationIfNeeded(
+                        packageName, packageUid, true, getUserAccounts(userId));
+            } catch (SQLiteCantOpenDatabaseException e) {
+                Log.w(TAG, "Can't read accounts database", e);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
 
     boolean getBindInstantServiceAllowed(int userId) {
         return  mAuthenticatorCache.getBindInstantServiceAllowed(userId);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index a84306b..0d309eb 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -739,8 +739,6 @@
     // Broadcast receiver for device connections intent broadcasts
     private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
 
-    private final Executor mAudioServerLifecycleExecutor;
-
     private IMediaProjectionManager mProjectionService; // to validate projection token
 
     /** Interface for UserManagerService. */
@@ -1061,8 +1059,7 @@
                               audioserverPermissions() ?
                                 initializeAudioServerPermissionProvider(
                                     context, audioPolicyFacade, audioserverLifecycleExecutor) :
-                                    null,
-                              audioserverLifecycleExecutor
+                                    null
                               );
         }
 
@@ -1148,16 +1145,13 @@
      *               {@link AudioSystemThread} is created as the messaging thread instead.
      * @param appOps {@link AppOpsManager} system service
      * @param enforcer Used for permission enforcing
-     * @param permissionProvider Used to push permissions to audioserver
-     * @param audioserverLifecycleExecutor Used for tasks managing audioserver lifecycle
      */
     @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
     public AudioService(Context context, AudioSystemAdapter audioSystem,
             SystemServerAdapter systemServer, SettingsAdapter settings,
             AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy,
             @Nullable Looper looper, AppOpsManager appOps, @NonNull PermissionEnforcer enforcer,
-            /* @NonNull */ AudioServerPermissionProvider permissionProvider,
-            Executor audioserverLifecycleExecutor) {
+            /* @NonNull */ AudioServerPermissionProvider permissionProvider) {
         super(enforcer);
         sLifecycleLogger.enqueue(new EventLogger.StringEvent("AudioService()"));
         mContext = context;
@@ -1165,7 +1159,6 @@
         mAppOps = appOps;
 
         mPermissionProvider = permissionProvider;
-        mAudioServerLifecycleExecutor = audioserverLifecycleExecutor;
 
         mAudioSystem = audioSystem;
         mSystemServer = systemServer;
@@ -1177,34 +1170,6 @@
         mBroadcastHandlerThread = new HandlerThread("AudioService Broadcast");
         mBroadcastHandlerThread.start();
 
-        // Listen to permission invalidations for the PermissionProvider
-        if (audioserverPermissions()) {
-            final Handler broadcastHandler = mBroadcastHandlerThread.getThreadHandler();
-            mAudioSystem.listenForSystemPropertyChange(PermissionManager.CACHE_KEY_PACKAGE_INFO,
-                    new Runnable() {
-                        // Roughly chosen to be long enough to suppress the autocork behavior
-                        // of the permission cache (50ms), and longer than the task could reasonably
-                        // take, even with many packages and users, while not introducing visible
-                        // permission leaks - since the app needs to restart, and trigger an action
-                        // which requires permissions from audioserver before this delay.
-                        // For RECORD_AUDIO, we are additionally protected by appops.
-                        final long UPDATE_DELAY_MS = 110;
-                        final AtomicLong scheduledUpdateTimestamp = new AtomicLong(0);
-                        @Override
-                        public void run() {
-                            var currentTime = SystemClock.uptimeMillis();
-                            if (currentTime > scheduledUpdateTimestamp.get()) {
-                                scheduledUpdateTimestamp.set(currentTime + UPDATE_DELAY_MS);
-                                broadcastHandler.postAtTime( () ->
-                                        mAudioServerLifecycleExecutor.execute(mPermissionProvider
-                                            ::onPermissionStateChanged),
-                                        currentTime + UPDATE_DELAY_MS
-                                    );
-                            }
-                        }
-            });
-        }
-
         mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
 
         mIsSingleVolume = AudioSystem.isSingleVolume(context);
@@ -12000,6 +11965,29 @@
             provider.onServiceStart(audioPolicy.getPermissionController());
         });
 
+        // Set up event listeners
+        // Must be kept in sync with PermissionManager
+        Runnable cacheSysPropHandler = new Runnable() {
+            private AtomicReference<SystemProperties.Handle> mHandle = new AtomicReference();
+            private AtomicLong mNonce = new AtomicLong();
+            @Override
+            public void run() {
+                if (mHandle.get() == null) {
+                    // Cache the handle
+                    mHandle.compareAndSet(null, SystemProperties.find(
+                            PermissionManager.CACHE_KEY_PACKAGE_INFO));
+                }
+                long nonce;
+                SystemProperties.Handle ref;
+                if ((ref = mHandle.get()) != null && (nonce = ref.getLong(0)) != 0 &&
+                        mNonce.getAndSet(nonce) != nonce) {
+                    audioserverExecutor.execute(() -> provider.onPermissionStateChanged());
+                }
+            }
+        };
+
+        SystemProperties.addChangeCallback(cacheSysPropHandler);
+
         IntentFilter packageUpdateFilter = new IntentFilter();
         packageUpdateFilter.addAction(ACTION_PACKAGE_ADDED);
         packageUpdateFilter.addAction(ACTION_PACKAGE_REMOVED);
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index d083c68..7f4bc74 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -748,10 +748,6 @@
         return AudioSystem.setMasterMute(mute);
     }
 
-    public void listenForSystemPropertyChange(String systemPropertyName, Runnable callback) {
-        AudioSystem.listenForSystemPropertyChange(systemPropertyName, callback);
-    }
-
     /**
      * Part of AudioService dump
      * @param pw
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index d3b41b8..e9ecfc6 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -25,6 +25,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.devicestate.DeviceState;
+import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.feature.flags.FeatureFlags;
 import android.hardware.devicestate.feature.flags.FeatureFlagsImpl;
 import android.os.Handler;
@@ -614,7 +615,11 @@
                     && isBootCompleted
                     && !mFoldSettingProvider.shouldStayAwakeOnFold();
         } else {
-            return mDeviceStatesOnWhichToSelectiveSleep.get(pendingState.getIdentifier())
+            return currentState.getIdentifier()
+                    != DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER
+                    && pendingState.getIdentifier()
+                    != DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER
+                    && mDeviceStatesOnWhichToSelectiveSleep.get(pendingState.getIdentifier())
                     && !mDeviceStatesOnWhichToSelectiveSleep.get(currentState.getIdentifier())
                     && isInteractive
                     && isBootCompleted
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index 5ff421a..7c93c8b 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -89,8 +89,8 @@
     void performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken,
             @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver,
             @SoftInputShowHideReason int reason, @UserIdInt int userId) {
-        final var bindingController = mService.getInputMethodBindingController(userId);
         final var userData = mService.getUserData(userId);
+        final var bindingController = userData.mBindingController;
         final IInputMethodInvoker curMethod = bindingController.getCurMethod();
         if (curMethod != null) {
             if (DEBUG) {
@@ -128,9 +128,9 @@
     void performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken,
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason,
             @UserIdInt int userId) {
-        final var bindingController = mService.getInputMethodBindingController(userId);
-        final IInputMethodInvoker curMethod = bindingController.getCurMethod();
         final var userData = mService.getUserData(userId);
+        final var bindingController = userData.mBindingController;
+        final IInputMethodInvoker curMethod = bindingController.getCurMethod();
         if (curMethod != null) {
             // The IME will report its visible state again after the following message finally
             // delivered to the IME process as an IPC.  Hence the inconsistency between
@@ -171,8 +171,8 @@
     void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
             @ImeVisibilityStateComputer.VisibilityState int state,
             @SoftInputShowHideReason int reason, @UserIdInt int userId) {
-        final var bindingController = mService.getInputMethodBindingController(userId);
         final var userData = mService.getUserData(userId);
+        final var bindingController = userData.mBindingController;
         final int displayIdToShowIme = bindingController.getDisplayIdToShowIme();
         switch (state) {
             case STATE_SHOW_IME:
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index c093c22..8fd2033 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -476,7 +476,7 @@
 
     @AnyThread
     @NonNull
-    UserDataRepository.UserData getUserData(@UserIdInt int userId) {
+    UserData getUserData(@UserIdInt int userId) {
         return mUserDataRepository.getOrCreate(userId);
     }
 
@@ -1422,15 +1422,15 @@
      * Returns true iff the caller is identified to be the current input method with the token.
      *
      * @param token the window token given to the input method when it was started
-     * @param userId userId of the calling IME process
+     * @param userData {@link UserData} of the calling IME process
      * @return true if and only if non-null valid token is specified
      */
     @GuardedBy("ImfLock.class")
-    private boolean calledWithValidTokenLocked(@NonNull IBinder token, @UserIdInt int userId) {
+    private boolean calledWithValidTokenLocked(@NonNull IBinder token, @NonNull UserData userData) {
         if (token == null) {
             throw new InvalidParameterException("token must not be null.");
         }
-        final var bindingController = getInputMethodBindingController(userId);
+        final var bindingController = userData.mBindingController;
         if (token != bindingController.getCurToken()) {
             Slog.e(TAG, "Ignoring " + Debug.getCaller() + " due to an invalid token."
                     + " uid:" + Binder.getCallingUid() + " token:" + token);
@@ -1709,7 +1709,7 @@
         clearClientSessionLocked(client);
         clearClientSessionForAccessibilityLocked(client);
         // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
-        @SuppressWarnings("GuardedBy") Consumer<UserDataRepository.UserData> clientRemovedForUser =
+        @SuppressWarnings("GuardedBy") Consumer<UserData> clientRemovedForUser =
                 userData -> onClientRemovedInternalLocked(client, userData);
         mUserDataRepository.forAllUserData(clientRemovedForUser);
     }
@@ -1719,15 +1719,14 @@
      */
     // TODO(b/325515685): Move this method to InputMethodBindingController
     @GuardedBy("ImfLock.class")
-    private void onClientRemovedInternalLocked(ClientState client,
-            @NonNull UserDataRepository.UserData userData) {
+    private void onClientRemovedInternalLocked(ClientState client, @NonNull UserData userData) {
         final int userId = userData.mUserId;
         if (userData.mCurClient == client) {
             hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */,
                     SoftInputShowHideReason.HIDE_REMOVE_CLIENT, userId);
             if (userData.mBoundToMethod) {
                 userData.mBoundToMethod = false;
-                final var userBindingController = getInputMethodBindingController(userId);
+                final var userBindingController = userData.mBindingController;
                 IInputMethodInvoker curMethod = userBindingController.getCurMethod();
                 if (curMethod != null) {
                     // When we unbind input, we are unbinding the client, so we always
@@ -1759,7 +1758,7 @@
                 Slog.v(TAG, "unbindCurrentInputLocked: client="
                         + userData.mCurClient.mClient.asBinder());
             }
-            final var bindingController = getInputMethodBindingController(userId);
+            final var bindingController = userData.mBindingController;
             if (userData.mBoundToMethod) {
                 userData.mBoundToMethod = false;
                 IInputMethodInvoker curMethod = bindingController.getCurMethod();
@@ -1845,8 +1844,8 @@
     @NonNull
     InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial,
             @UserIdInt int userId) {
-        final var bindingController = getInputMethodBindingController(userId);
         final var userData = getUserData(userId);
+        final var bindingController = userData.mBindingController;
         if (!userData.mBoundToMethod) {
             bindingController.getCurMethod().bindInput(userData.mCurClient.mBinding);
             userData.mBoundToMethod = true;
@@ -2275,9 +2274,10 @@
                     + bindingController.getCurTokenDisplayId());
         }
         final int userId = bindingController.getUserId();
+        final var userData = getUserData(userId);
         inputMethod.initializeInternal(token,
-                new InputMethodPrivilegedOperationsImpl(this, token, userId),
-                getInputMethodNavButtonFlagsLocked(getUserData(userId)));
+                new InputMethodPrivilegedOperationsImpl(this, token, userData),
+                getInputMethodNavButtonFlagsLocked(userData));
     }
 
     @AnyThread
@@ -2317,8 +2317,8 @@
                     channel.dispose();
                     return;
                 }
-                final var bindingController = getInputMethodBindingController(userId);
                 final var userData = getUserData(userId);
+                final var bindingController = userData.mBindingController;
                 IInputMethodInvoker curMethod = bindingController.getCurMethod();
                 if (curMethod != null && method != null
                         && curMethod.asBinder() == method.asBinder()) {
@@ -2531,9 +2531,10 @@
 
     @BinderThread
     private void updateStatusIcon(@NonNull IBinder token, String packageName,
-            @DrawableRes int iconId, @UserIdInt int userId) {
+            @DrawableRes int iconId, @NonNull UserData userData) {
+        final int userId = userData.mUserId;
         synchronized (ImfLock.class) {
-            if (!calledWithValidTokenLocked(token, userId)) {
+            if (!calledWithValidTokenLocked(token, userData)) {
                 return;
             }
             final long ident = Binder.clearCallingIdentity();
@@ -2576,8 +2577,7 @@
 
     @GuardedBy("ImfLock.class")
     @InputMethodNavButtonFlags
-    private int getInputMethodNavButtonFlagsLocked(
-            @NonNull UserDataRepository.UserData userData) {
+    private int getInputMethodNavButtonFlagsLocked(@NonNull UserData userData) {
         final int userId = userData.mUserId;
         final var bindingController = userData.mBindingController;
         // Whether the current display has a navigation bar. When this is false (e.g. emulator),
@@ -2672,14 +2672,15 @@
     @BinderThread
     @SuppressWarnings("deprecation")
     private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition,
-            @UserIdInt int userId) {
+            @NonNull UserData userData) {
         final int topFocusedDisplayId = mWindowManagerInternal.getTopFocusedDisplayId();
 
+        final int userId = userData.mUserId;
         synchronized (ImfLock.class) {
-            if (!calledWithValidTokenLocked(token, userId)) {
+            if (!calledWithValidTokenLocked(token, userData)) {
                 return;
             }
-            final var bindingController = getInputMethodBindingController(userId);
+            final var bindingController = userData.mBindingController;
             // Skip update IME status when current token display is not same as focused display.
             // Note that we still need to update IME status when focusing external display
             // that does not support system decoration and fallback to show IME on default
@@ -2711,9 +2712,9 @@
 
     @BinderThread
     private void reportStartInput(@NonNull IBinder token, IBinder startInputToken,
-            @UserIdInt int userId) {
+            @NonNull UserData userData) {
         synchronized (ImfLock.class) {
-            if (!calledWithValidTokenLocked(token, userId)) {
+            if (!calledWithValidTokenLocked(token, userData)) {
                 return;
             }
             final IBinder targetWindow = mImeTargetWindowMap.get(startInputToken);
@@ -2747,8 +2748,8 @@
 
     @GuardedBy("ImfLock.class")
     private void updateSystemUiLocked(int vis, int backDisposition, @UserIdInt int userId) {
-        final var bindingController = getInputMethodBindingController(userId);
         final var userData = getUserData(userId);
+        final var bindingController = userData.mBindingController;
         final var curToken = bindingController.getCurToken();
         if (curToken == null) {
             return;
@@ -2846,11 +2847,11 @@
                 settings.putSelectedInputMethod(id);
             }
         }
-        final var bindingController = getInputMethodBindingController(userId);
+        final var userData = getUserData(userId);
+        final var bindingController = userData.mBindingController;
         bindingController.setSelectedMethodId(id);
 
         // Also re-initialize controllers.
-        final var userData = getUserData(userId);
         userData.mSwitchingController.resetCircularListLocked(mContext, settings);
         userData.mHardwareKeyboardShortcutController.update(settings);
     }
@@ -2887,7 +2888,8 @@
             }
         }
 
-        final var bindingController = getInputMethodBindingController(userId);
+        final var userData = getUserData(userId);
+        final var bindingController = userData.mBindingController;
         if (bindingController.getDeviceIdToShowIme() == DEVICE_ID_DEFAULT) {
             String ime = SecureSettingsWrapper.getString(
                     Settings.Secure.DEFAULT_INPUT_METHOD, null, userId);
@@ -2927,7 +2929,6 @@
             resetCurrentMethodAndClientLocked(UnbindReason.NO_IME, userId);
         }
 
-        final var userData = getUserData(userId);
         userData.mSwitchingController.resetCircularListLocked(mContext, settings);
         userData.mHardwareKeyboardShortcutController.update(settings);
         sendOnNavButtonFlagsChangedLocked(userData);
@@ -3409,8 +3410,8 @@
         mVisibilityStateComputer.requestImeVisibility(windowToken, true);
 
         // Ensure binding the connection when IME is going to show.
-        final var bindingController = getInputMethodBindingController(userId);
         final var userData = getUserData(userId);
+        final var bindingController = userData.mBindingController;
         bindingController.setCurrentMethodVisible();
         final IInputMethodInvoker curMethod = bindingController.getCurMethod();
         ImeTracker.forLogging().onCancelled(userData.mCurStatsToken,
@@ -3543,7 +3544,8 @@
     boolean hideCurrentInputLocked(IBinder windowToken, @NonNull ImeTracker.Token statsToken,
             @InputMethodManager.HideFlags int flags, @Nullable ResultReceiver resultReceiver,
             @SoftInputShowHideReason int reason, @UserIdInt int userId) {
-        final var bindingController = getInputMethodBindingController(userId);
+        final var userData = getUserData(userId);
+        final var bindingController = userData.mBindingController;
         if (!mVisibilityStateComputer.canHideIme(statsToken, flags)) {
             return false;
         }
@@ -3556,7 +3558,6 @@
         // since Android Eclair.  That's why we need to accept IMM#hideSoftInput() even when only
         // IMMS#InputShown indicates that the software keyboard is shown.
         // TODO(b/246309664): Clean up IMMS#mImeWindowVis
-        final var userData = getUserData(userId);
         IInputMethodInvoker curMethod = bindingController.getCurMethod();
         final boolean shouldHideSoftInput = curMethod != null
                 && (isInputShownLocked()
@@ -3636,6 +3637,7 @@
             Slog.w(TAG, "User #" + userId + " is not running.");
             return InputBindResult.INVALID_USER;
         }
+        final var userData = getUserData(userId);
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
                     "IMMS.startInputOrWindowGainedFocus");
@@ -3643,7 +3645,7 @@
                     "InputMethodManagerService#startInputOrWindowGainedFocus", mDumper);
             final InputBindResult result;
             synchronized (ImfLock.class) {
-                final var bindingController = getInputMethodBindingController(userId);
+                final var bindingController = userData.mBindingController;
                 // If the system is not yet ready, we shouldn't be running third party code.
                 if (!mSystemReady) {
                     return new InputBindResult(
@@ -3708,7 +3710,6 @@
                     final boolean shouldClearFlag =
                             mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid);
                     final boolean showForced = mVisibilityStateComputer.mShowForced;
-                    final var userData = getUserData(userId);
                     if (userData.mImeBindingState.mFocusedWindow != windowToken
                             && showForced && shouldClearFlag) {
                         mVisibilityStateComputer.mShowForced = false;
@@ -3970,13 +3971,9 @@
 
     @BinderThread
     private void onImeSwitchButtonClickFromClient(@NonNull IBinder token, int displayId,
-            @UserIdInt int userId) {
-        userId = mActivityManagerInternal.handleIncomingUser(
-                Binder.getCallingPid(), Binder.getCallingUid(), userId, false,
-                ActivityManagerInternal.ALLOW_FULL_ONLY, "onImeSwitchButtonClickFromClient", null);
-
+            @NonNull UserData userData) {
         synchronized (ImfLock.class) {
-            if (!calledWithValidTokenLocked(token, userId)) {
+            if (!calledWithValidTokenLocked(token, userData)) {
                 return;
             }
             showInputMethodPickerFromSystem(
@@ -3998,10 +3995,11 @@
     }
 
     @BinderThread
-    private void setInputMethod(@NonNull IBinder token, String id, @UserIdInt int userId) {
+    private void setInputMethod(@NonNull IBinder token, String id, @NonNull UserData userData) {
         final int callingUid = Binder.getCallingUid();
+        final int userId = userData.mUserId;
         synchronized (ImfLock.class) {
-            if (!calledWithValidTokenLocked(token, userId)) {
+            if (!calledWithValidTokenLocked(token, userData)) {
                 return;
             }
             final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
@@ -4016,10 +4014,11 @@
 
     @BinderThread
     private void setInputMethodAndSubtype(@NonNull IBinder token, String id,
-            InputMethodSubtype subtype, @UserIdInt int userId) {
+            InputMethodSubtype subtype, @NonNull UserData userData) {
         final int callingUid = Binder.getCallingUid();
+        final int userId = userData.mUserId;
         synchronized (ImfLock.class) {
-            if (!calledWithValidTokenLocked(token, userId)) {
+            if (!calledWithValidTokenLocked(token, userData)) {
                 return;
             }
             final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
@@ -4032,18 +4031,20 @@
                 setInputMethodWithSubtypeIdLocked(token, id,
                         SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()), userId);
             } else {
-                setInputMethod(token, id, userId);
+                setInputMethod(token, id, userData);
             }
         }
     }
 
     @BinderThread
-    private boolean switchToPreviousInputMethod(@NonNull IBinder token, @UserIdInt int userId) {
+    private boolean switchToPreviousInputMethod(@NonNull IBinder token,
+            @NonNull UserData userData) {
+        final int userId = userData.mUserId;
         synchronized (ImfLock.class) {
-            if (!calledWithValidTokenLocked(token, userId)) {
+            if (!calledWithValidTokenLocked(token, userData)) {
                 return false;
             }
-            final var bindingController = getInputMethodBindingController(userId);
+            final var bindingController = userData.mBindingController;
             final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
             final Pair<String, String> lastIme = settings.getLastInputMethodAndSubtype();
             final InputMethodInfo lastImi;
@@ -4120,21 +4121,21 @@
 
     @BinderThread
     private boolean switchToNextInputMethod(@NonNull IBinder token, boolean onlyCurrentIme,
-            @UserIdInt int userId) {
+            @NonNull UserData userData) {
         synchronized (ImfLock.class) {
-            if (!calledWithValidTokenLocked(token, userId)) {
+            if (!calledWithValidTokenLocked(token, userData)) {
                 return false;
             }
-            return switchToNextInputMethodLocked(token, onlyCurrentIme, userId);
+            return switchToNextInputMethodLocked(token, onlyCurrentIme, userData);
         }
     }
 
     @GuardedBy("ImfLock.class")
     private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme,
-            @UserIdInt int userId) {
-        final var bindingController = getInputMethodBindingController(userId);
+            @NonNull UserData userData) {
+        final var bindingController = userData.mBindingController;
         final var currentImi = bindingController.getSelectedMethod();
-        final ImeSubtypeListItem nextSubtype = getUserData(userId).mSwitchingController
+        final ImeSubtypeListItem nextSubtype = userData.mSwitchingController
                 .getNextInputMethodLocked(onlyCurrentIme, currentImi,
                         bindingController.getCurrentSubtype(),
                         MODE_AUTO, true /* forward */);
@@ -4142,20 +4143,20 @@
             return false;
         }
         setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(),
-                nextSubtype.mSubtypeId, userId);
+                nextSubtype.mSubtypeId, userData.mUserId);
         return true;
     }
 
     @BinderThread
     private boolean shouldOfferSwitchingToNextInputMethod(@NonNull IBinder token,
-            @UserIdInt int userId) {
+            @NonNull UserData userData) {
         synchronized (ImfLock.class) {
-            if (!calledWithValidTokenLocked(token, userId)) {
+            if (!calledWithValidTokenLocked(token, userData)) {
                 return false;
             }
-            final var bindingController = getInputMethodBindingController(userId);
+            final var bindingController = userData.mBindingController;
             final var currentImi = bindingController.getSelectedMethod();
-            final ImeSubtypeListItem nextSubtype = getUserData(userId).mSwitchingController
+            final ImeSubtypeListItem nextSubtype = userData.mSwitchingController
                     .getNextInputMethodLocked(false /* onlyCurrentIme */, currentImi,
                             bindingController.getCurrentSubtype(),
                             MODE_AUTO, true /* forward */);
@@ -4556,8 +4557,8 @@
     private void dumpDebug(ProtoOutputStream proto, long fieldId) {
         synchronized (ImfLock.class) {
             final int userId = mCurrentUserId;
-            final var bindingController = getInputMethodBindingController(userId);
             final var userData = getUserData(userId);
+            final var bindingController = userData.mBindingController;
             final long token = proto.start(fieldId);
             proto.write(CUR_METHOD_ID, bindingController.getSelectedMethodId());
             proto.write(CUR_SEQ, bindingController.getSequenceNumber());
@@ -4588,12 +4589,12 @@
     }
 
     @BinderThread
-    private void notifyUserAction(@NonNull IBinder token, @UserIdInt int userId) {
+    private void notifyUserAction(@NonNull IBinder token, @NonNull UserData userData) {
         if (DEBUG) {
             Slog.d(TAG, "Got the notification of a user action.");
         }
         synchronized (ImfLock.class) {
-            final var bindingController = getInputMethodBindingController(userId);
+            final var bindingController = userData.mBindingController;
             if (bindingController.getCurToken() != token) {
                 if (DEBUG) {
                     Slog.d(TAG, "Ignoring the user action notification from IMEs that are no longer"
@@ -4603,7 +4604,7 @@
             }
             final InputMethodInfo imi = bindingController.getSelectedMethod();
             if (imi != null) {
-                getUserData(userId).mSwitchingController.onUserActionLocked(imi,
+                userData.mSwitchingController.onUserActionLocked(imi,
                         bindingController.getCurrentSubtype());
             }
         }
@@ -4611,11 +4612,12 @@
 
     @BinderThread
     private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible,
-            @NonNull ImeTracker.Token statsToken, @UserIdInt int userId) {
+            @NonNull ImeTracker.Token statsToken, @NonNull UserData userData) {
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility");
+            final int userId = userData.mUserId;
             synchronized (ImfLock.class) {
-                if (!calledWithValidTokenLocked(token, userId)) {
+                if (!calledWithValidTokenLocked(token, userData)) {
                     ImeTracker.forLogging().onFailed(statsToken,
                             ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
                     return;
@@ -4690,8 +4692,8 @@
             @UserIdInt int userId) {
         final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken,
                 userId);
-        final var bindingController = getInputMethodBindingController(userId);
         final var userData = getUserData(userId);
+        final var bindingController = userData.mBindingController;
         final WindowManagerInternal.ImeTargetInfo info =
                 mWindowManagerInternal.onToggleImeRequested(
                         show, userData.mImeBindingState.mFocusedWindow, requestToken,
@@ -4711,16 +4713,16 @@
     @BinderThread
     private void hideMySoftInput(@NonNull IBinder token, @NonNull ImeTracker.Token statsToken,
             @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason,
-            @UserIdInt int userId) {
+            @NonNull UserData userData) {
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput");
+            final int userId = userData.mUserId;
             synchronized (ImfLock.class) {
-                if (!calledWithValidTokenLocked(token, userId)) {
+                if (!calledWithValidTokenLocked(token, userData)) {
                     ImeTracker.forLogging().onFailed(statsToken,
                             ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
                     return;
                 }
-                final var userData = getUserData(userId);
                 ImeTracker.forLogging().onProgress(statsToken,
                         ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
                 final long ident = Binder.clearCallingIdentity();
@@ -4750,16 +4752,16 @@
     @BinderThread
     private void showMySoftInput(@NonNull IBinder token, @NonNull ImeTracker.Token statsToken,
             @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason,
-            @UserIdInt int userId) {
+            @NonNull UserData userData) {
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showMySoftInput");
+            final int userId = userData.mUserId;
             synchronized (ImfLock.class) {
-                if (!calledWithValidTokenLocked(token, userId)) {
+                if (!calledWithValidTokenLocked(token, userData)) {
                     ImeTracker.forLogging().onFailed(statsToken,
                             ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
                     return;
                 }
-                final var userData = getUserData(userId);
                 ImeTracker.forLogging().onProgress(statsToken,
                         ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
                 final long ident = Binder.clearCallingIdentity();
@@ -4804,8 +4806,7 @@
     }
 
     @GuardedBy("ImfLock.class")
-    void setEnabledSessionLocked(SessionState session,
-            @NonNull UserDataRepository.UserData userData) {
+    void setEnabledSessionLocked(SessionState session, @NonNull UserData userData) {
         if (userData.mEnabledSession != session) {
             if (userData.mEnabledSession != null && userData.mEnabledSession.mSession != null) {
                 if (DEBUG) Slog.v(TAG, "Disabling: " + userData.mEnabledSession);
@@ -4824,7 +4825,7 @@
     @GuardedBy("ImfLock.class")
     void setEnabledSessionForAccessibilityLocked(
             SparseArray<AccessibilitySessionState> accessibilitySessions,
-            @NonNull UserDataRepository.UserData userData) {
+            @NonNull UserData userData) {
         // mEnabledAccessibilitySessions could the same object as accessibilitySessions.
         SparseArray<IAccessibilityInputMethodSession> disabledSessions = new SparseArray<>();
         for (int i = 0; i < userData.mEnabledAccessibilitySessions.size(); i++) {
@@ -5033,9 +5034,8 @@
             case MSG_START_HANDWRITING:
                 final var handwritingRequest = (HandwritingRequest) msg.obj;
                 synchronized (ImfLock.class) {
-                    final int userId = handwritingRequest.userId;
-                    final var bindingController = getInputMethodBindingController(userId);
-                    final var userData = getUserData(userId);
+                    final var userData = handwritingRequest.userData;
+                    final var bindingController = userData.mBindingController;
                     IInputMethodInvoker curMethod = bindingController.getCurMethod();
                     if (curMethod == null || userData.mImeBindingState.mFocusedWindow == null) {
                         return true;
@@ -5080,24 +5080,24 @@
         return false;
     }
 
-    private record HandwritingRequest(int requestId, int pid, @UserIdInt int userId) { }
+    private record HandwritingRequest(int requestId, int pid, @NonNull UserData userData) { }
 
     @BinderThread
-    private void onStylusHandwritingReady(int requestId, int pid, @UserIdInt int userId) {
+    private void onStylusHandwritingReady(int requestId, int pid, @NonNull UserData userData) {
         mHandler.obtainMessage(MSG_START_HANDWRITING,
-                new HandwritingRequest(requestId, pid, userId)).sendToTarget();
+                new HandwritingRequest(requestId, pid, userData)).sendToTarget();
     }
 
     private void handleSetInteractive(final boolean interactive) {
         synchronized (ImfLock.class) {
             // TODO(b/305849394): Support multiple IMEs.
             final int userId = mCurrentUserId;
-            final var bindingController = getInputMethodBindingController(userId);
+            final var userData = getUserData(userId);
+            final var bindingController = userData.mBindingController;
             mIsInteractive = interactive;
             updateSystemUiLocked(
                     interactive ? bindingController.getImeWindowVis() : 0,
                     bindingController.getBackDisposition(), userId);
-            final var userData = getUserData(userId);
             // Inform the current client of the change in active status
             if (userData.mCurClient == null || userData.mCurClient.mClient == null) {
                 return;
@@ -5329,7 +5329,7 @@
     }
 
     @GuardedBy("ImfLock.class")
-    void sendOnNavButtonFlagsChangedLocked(@NonNull UserDataRepository.UserData userData) {
+    void sendOnNavButtonFlagsChangedLocked(@NonNull UserData userData) {
         final var bindingController = userData.mBindingController;
         final IInputMethodInvoker curMethod = bindingController.getCurMethod();
         if (curMethod == null) {
@@ -5345,7 +5345,7 @@
         final boolean value =
                 InputMethodDrawsNavBarResourceMonitor.evaluate(mContext, profileParentId);
         final var profileUserIds = mUserManagerInternal.getProfileIds(profileParentId, false);
-        final ArrayList<UserDataRepository.UserData> updatedUsers = new ArrayList<>();
+        final ArrayList<UserData> updatedUsers = new ArrayList<>();
         for (int profileUserId : profileUserIds) {
             final var userData = getUserData(profileUserId);
             userData.mImeDrawsNavBar.set(value);
@@ -5598,10 +5598,11 @@
     }
 
     @GuardedBy("ImfLock.class")
-    private void switchKeyboardLayoutLocked(int direction, @UserIdInt int userId) {
+    private void switchKeyboardLayoutLocked(int direction, @NonNull UserData userData) {
+        final int userId = userData.mUserId;
         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
 
-        final var bindingController = getInputMethodBindingController(userId);
+        final var bindingController = userData.mBindingController;
         final InputMethodInfo currentImi = settings.getMethodMap().get(
                 bindingController.getSelectedMethodId());
         if (currentImi == null) {
@@ -5610,7 +5611,7 @@
         final var currentSubtype = bindingController.getCurrentSubtype();
         final InputMethodSubtypeHandle nextSubtypeHandle;
         if (Flags.imeSwitcherRevamp()) {
-            final var nextItem = getUserData(userId).mSwitchingController
+            final var nextItem = userData.mSwitchingController
                     .getNextInputMethodForHardware(
                             false /* onlyCurrentIme */, currentImi, currentSubtype, MODE_AUTO,
                             direction > 0 /* forward */);
@@ -5626,8 +5627,7 @@
         } else {
             final InputMethodSubtypeHandle currentSubtypeHandle =
                     InputMethodSubtypeHandle.of(currentImi, currentSubtype);
-            nextSubtypeHandle =
-                    getUserData(userId).mHardwareKeyboardShortcutController.onSubtypeSwitch(
+            nextSubtypeHandle = userData.mHardwareKeyboardShortcutController.onSubtypeSwitch(
                         currentSubtypeHandle, direction > 0);
         }
         if (nextSubtypeHandle == null) {
@@ -5775,7 +5775,7 @@
                 if (displayId != bindingController.getCurTokenDisplayId()) {
                     return false;
                 }
-                curHostInputToken = getInputMethodBindingController(userId).getCurHostInputToken();
+                curHostInputToken = bindingController.getCurHostInputToken();
                 if (curHostInputToken == null) {
                     return false;
                 }
@@ -5831,8 +5831,8 @@
         public void onSessionForAccessibilityCreated(int accessibilityConnectionId,
                 IAccessibilityInputMethodSession session, @UserIdInt int userId) {
             synchronized (ImfLock.class) {
-                final var bindingController = getInputMethodBindingController(userId);
                 final var userData = getUserData(userId);
+                final var bindingController = userData.mBindingController;
                 // TODO(b/305829876): Implement user ID verification
                 if (userData.mCurClient != null) {
                     clearClientSessionForAccessibilityLocked(userData.mCurClient,
@@ -5869,8 +5869,8 @@
         public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId,
                 @UserIdInt int userId) {
             synchronized (ImfLock.class) {
-                final var bindingController = getInputMethodBindingController(userId);
                 final var userData = getUserData(userId);
+                final var bindingController = userData.mBindingController;
                 // TODO(b/305829876): Implement user ID verification
                 if (userData.mCurClient != null) {
                     if (DEBUG) {
@@ -5914,14 +5914,14 @@
                 IBinder targetWindowToken) {
             synchronized (ImfLock.class) {
                 // TODO(b/305849394): Infer userId from displayId
-                switchKeyboardLayoutLocked(direction, mCurrentUserId);
+                switchKeyboardLayoutLocked(direction, getUserData(mCurrentUserId));
             }
         }
     }
 
     @BinderThread
     private IInputContentUriToken createInputContentUriToken(@Nullable IBinder token,
-            @Nullable Uri contentUri, @Nullable String packageName, @UserIdInt int imeUserId) {
+            @Nullable Uri contentUri, @Nullable String packageName, @NonNull UserData userData) {
         if (token == null) {
             throw new NullPointerException("token");
         }
@@ -5938,7 +5938,7 @@
 
         synchronized (ImfLock.class) {
             final int uid = Binder.getCallingUid();
-            final var bindingController = getInputMethodBindingController(imeUserId);
+            final var bindingController = userData.mBindingController;
             if (bindingController.getSelectedMethodId() == null) {
                 return null;
             }
@@ -5950,7 +5950,6 @@
             // We cannot simply distinguish a bad IME that reports an arbitrary package name from
             // an unfortunate IME whose internal state is already obsolete due to the asynchronous
             // nature of our system.  Let's compare it with our internal record.
-            final var userData = getUserData(imeUserId);
             final var curPackageName = userData.mCurEditorInfo != null
                     ? userData.mCurEditorInfo.packageName : null;
             if (!TextUtils.equals(curPackageName, packageName)) {
@@ -5962,7 +5961,7 @@
             final int appUserId = UserHandle.getUserId(userData.mCurClient.mUid);
             // This user ID may be invalid if "contentUri" embedded an invalid user ID.
             final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(contentUri,
-                    imeUserId);
+                    userData.mUserId);
             final Uri contentUriWithoutUserId = ContentProvider.getUriWithoutUserId(contentUri);
             // Note: InputContentUriTokenHandler.take() checks whether the IME (specified by "uid")
             // actually has the right to grant a read permission for "contentUriWithoutUserId" that
@@ -5977,12 +5976,11 @@
 
     @BinderThread
     private void reportFullscreenMode(@NonNull IBinder token, boolean fullscreen,
-            @UserIdInt int userId) {
+            @NonNull UserData userData) {
         synchronized (ImfLock.class) {
-            if (!calledWithValidTokenLocked(token, userId)) {
+            if (!calledWithValidTokenLocked(token, userData)) {
                 return;
             }
-            final var userData = getUserData(userId);
             if (userData.mCurClient != null && userData.mCurClient.mClient != null) {
                 userData.mInFullscreenMode = fullscreen;
                 userData.mCurClient.mClient.reportFullscreenMode(fullscreen);
@@ -6109,8 +6107,8 @@
                 p.println("    pid=" + c.mPid);
             };
             mClientController.forAllClients(clientControllerDump);
-            final var bindingController = getInputMethodBindingController(mCurrentUserId);
-            p.println("  mCurrentUserId=" + mCurrentUserId);
+            final var bindingController = userData.mBindingController;
+            p.println("  mCurrentUserId=" + userData.mUserId);
             p.println("  mCurMethodId=" + bindingController.getSelectedMethodId());
             client = userData.mCurClient;
             p.println("  mCurClient=" + client + " mCurSeq="
@@ -6125,7 +6123,7 @@
 
             p.println("  mUserDataRepository=");
             // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
-            @SuppressWarnings("GuardedBy") Consumer<UserDataRepository.UserData> userDataDump =
+            @SuppressWarnings("GuardedBy") Consumer<UserData> userDataDump =
                     u -> {
                         p.println("    mUserId=" + u.mUserId);
                         p.println("      hasMainConnection="
@@ -6662,7 +6660,7 @@
                                     0 /* flags */,
                                     SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND, userId);
                         }
-                        final var bindingController = getInputMethodBindingController(userId);
+                        final var bindingController = userData.mBindingController;
                         bindingController.unbindCurrentMethod();
 
                         // Enable default IMEs, disable others
@@ -6810,26 +6808,26 @@
         private final InputMethodManagerService mImms;
         @NonNull
         private final IBinder mToken;
-        @UserIdInt
-        private final int mUserId;
+        @NonNull
+        private final UserData mUserData;
 
         InputMethodPrivilegedOperationsImpl(InputMethodManagerService imms,
-                @NonNull IBinder token, @UserIdInt int userId) {
+                @NonNull IBinder token, @NonNull UserData userData) {
             mImms = imms;
             mToken = token;
-            mUserId = userId;
+            mUserData = userData;
         }
 
         @BinderThread
         @Override
         public void setImeWindowStatusAsync(int vis, int backDisposition) {
-            mImms.setImeWindowStatus(mToken, vis, backDisposition, mUserId);
+            mImms.setImeWindowStatus(mToken, vis, backDisposition, mUserData);
         }
 
         @BinderThread
         @Override
         public void reportStartInputAsync(IBinder startInputToken) {
-            mImms.reportStartInput(mToken, startInputToken, mUserId);
+            mImms.reportStartInput(mToken, startInputToken, mUserData);
         }
 
         @BinderThread
@@ -6845,7 +6843,7 @@
             @SuppressWarnings("unchecked") final AndroidFuture<IBinder> typedFuture = future;
             try {
                 typedFuture.complete(mImms.createInputContentUriToken(
-                        mToken, contentUri, packageName, mUserId).asBinder());
+                        mToken, contentUri, packageName, mUserData).asBinder());
             } catch (Throwable e) {
                 typedFuture.completeExceptionally(e);
             }
@@ -6854,7 +6852,7 @@
         @BinderThread
         @Override
         public void reportFullscreenModeAsync(boolean fullscreen) {
-            mImms.reportFullscreenMode(mToken, fullscreen, mUserId);
+            mImms.reportFullscreenMode(mToken, fullscreen, mUserData);
         }
 
         @BinderThread
@@ -6862,7 +6860,7 @@
         public void setInputMethod(String id, AndroidFuture future /* T=Void */) {
             @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
             try {
-                mImms.setInputMethod(mToken, id, mUserId);
+                mImms.setInputMethod(mToken, id, mUserData);
                 typedFuture.complete(null);
             } catch (Throwable e) {
                 typedFuture.completeExceptionally(e);
@@ -6875,7 +6873,7 @@
                 AndroidFuture future /* T=Void */) {
             @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
             try {
-                mImms.setInputMethodAndSubtype(mToken, id, subtype, mUserId);
+                mImms.setInputMethodAndSubtype(mToken, id, subtype, mUserData);
                 typedFuture.complete(null);
             } catch (Throwable e) {
                 typedFuture.completeExceptionally(e);
@@ -6889,7 +6887,7 @@
                 AndroidFuture future /* T=Void */) {
             @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
             try {
-                mImms.hideMySoftInput(mToken, statsToken, flags, reason, mUserId);
+                mImms.hideMySoftInput(mToken, statsToken, flags, reason, mUserData);
                 typedFuture.complete(null);
             } catch (Throwable e) {
                 typedFuture.completeExceptionally(e);
@@ -6903,7 +6901,7 @@
                 AndroidFuture future /* T=Void */) {
             @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
             try {
-                mImms.showMySoftInput(mToken, statsToken, flags, reason, mUserId);
+                mImms.showMySoftInput(mToken, statsToken, flags, reason, mUserData);
                 typedFuture.complete(null);
             } catch (Throwable e) {
                 typedFuture.completeExceptionally(e);
@@ -6913,7 +6911,7 @@
         @BinderThread
         @Override
         public void updateStatusIconAsync(String packageName, @DrawableRes int iconId) {
-            mImms.updateStatusIcon(mToken, packageName, iconId, mUserId);
+            mImms.updateStatusIcon(mToken, packageName, iconId, mUserData);
         }
 
         @BinderThread
@@ -6921,7 +6919,7 @@
         public void switchToPreviousInputMethod(AndroidFuture future /* T=Boolean */) {
             @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
             try {
-                typedFuture.complete(mImms.switchToPreviousInputMethod(mToken, mUserId));
+                typedFuture.complete(mImms.switchToPreviousInputMethod(mToken, mUserData));
             } catch (Throwable e) {
                 typedFuture.completeExceptionally(e);
             }
@@ -6934,7 +6932,7 @@
             @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
             try {
                 typedFuture.complete(mImms.switchToNextInputMethod(mToken, onlyCurrentIme,
-                        mUserId));
+                        mUserData));
             } catch (Throwable e) {
                 typedFuture.completeExceptionally(e);
             }
@@ -6945,7 +6943,8 @@
         public void shouldOfferSwitchingToNextInputMethod(AndroidFuture future /* T=Boolean */) {
             @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
             try {
-                typedFuture.complete(mImms.shouldOfferSwitchingToNextInputMethod(mToken, mUserId));
+                typedFuture.complete(mImms.shouldOfferSwitchingToNextInputMethod(mToken,
+                        mUserData));
             } catch (Throwable e) {
                 typedFuture.completeExceptionally(e);
             }
@@ -6953,27 +6952,27 @@
 
         @BinderThread
         @Override
-        public void onImeSwitchButtonClickFromClient(int displayId, @UserIdInt int userId) {
-            mImms.onImeSwitchButtonClickFromClient(mToken, displayId, userId);
+        public void onImeSwitchButtonClickFromClient(int displayId) {
+            mImms.onImeSwitchButtonClickFromClient(mToken, displayId, mUserData);
         }
 
         @BinderThread
         @Override
         public void notifyUserActionAsync() {
-            mImms.notifyUserAction(mToken, mUserId);
+            mImms.notifyUserAction(mToken, mUserData);
         }
 
         @BinderThread
         @Override
         public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible,
                 @NonNull ImeTracker.Token statsToken) {
-            mImms.applyImeVisibility(mToken, windowToken, setVisible, statsToken, mUserId);
+            mImms.applyImeVisibility(mToken, windowToken, setVisible, statsToken, mUserData);
         }
 
         @BinderThread
         @Override
         public void onStylusHandwritingReady(int requestId, int pid) {
-            mImms.onStylusHandwritingReady(requestId, pid, mUserId);
+            mImms.onStylusHandwritingReady(requestId, pid, mUserData);
         }
 
         @BinderThread
@@ -6986,12 +6985,12 @@
         @Override
         public void switchKeyboardLayoutAsync(int direction) {
             synchronized (ImfLock.class) {
-                if (!mImms.calledWithValidTokenLocked(mToken, mUserId)) {
+                if (!mImms.calledWithValidTokenLocked(mToken, mUserData)) {
                     return;
                 }
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    mImms.switchKeyboardLayoutLocked(direction, mUserId);
+                    mImms.switchKeyboardLayoutLocked(direction, mUserData);
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
diff --git a/services/core/java/com/android/server/inputmethod/UserData.java b/services/core/java/com/android/server/inputmethod/UserData.java
new file mode 100644
index 0000000..ec5c9e6
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/UserData.java
@@ -0,0 +1,147 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.util.SparseArray;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
+import android.window.ImeOnBackInvokedDispatcher;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** Placeholder for all IMMS user specific fields */
+final class UserData {
+    @UserIdInt
+    final int mUserId;
+
+    @NonNull
+    final InputMethodBindingController mBindingController;
+
+    @NonNull
+    final InputMethodSubtypeSwitchingController mSwitchingController =
+            new InputMethodSubtypeSwitchingController();
+
+    @NonNull
+    final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController =
+            new HardwareKeyboardShortcutController();
+
+    /**
+     * Have we called mCurMethod.bindInput()?
+     */
+    @GuardedBy("ImfLock.class")
+    boolean mBoundToMethod = false;
+
+    /**
+     * Have we called bindInput() for accessibility services?
+     */
+    @GuardedBy("ImfLock.class")
+    boolean mBoundToAccessibility;
+
+    @GuardedBy("ImfLock.class")
+    @NonNull
+    ImeBindingState mImeBindingState = ImeBindingState.newEmptyState();
+
+    @GuardedBy("ImfLock.class")
+    @Nullable
+    ClientState mCurClient = null;
+
+    @GuardedBy("ImfLock.class")
+    boolean mInFullscreenMode;
+
+    /**
+     * The {@link IRemoteInputConnection} last provided by the current client.
+     */
+    @GuardedBy("ImfLock.class")
+    @Nullable
+    IRemoteInputConnection mCurInputConnection;
+
+    /**
+     * The {@link ImeOnBackInvokedDispatcher} last provided by the current client to
+     * receive {@link android.window.OnBackInvokedCallback}s forwarded from IME.
+     */
+    @GuardedBy("ImfLock.class")
+    @Nullable
+    ImeOnBackInvokedDispatcher mCurImeDispatcher;
+
+    /**
+     * The {@link IRemoteAccessibilityInputConnection} last provided by the current client.
+     */
+    @GuardedBy("ImfLock.class")
+    @Nullable
+    IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection;
+
+    /**
+     * The {@link EditorInfo} last provided by the current client.
+     */
+    @GuardedBy("ImfLock.class")
+    @Nullable
+    EditorInfo mCurEditorInfo;
+
+    /**
+     * The token tracking the current IME show request that is waiting for a connection to an
+     * IME, otherwise {@code null}.
+     */
+    @GuardedBy("ImfLock.class")
+    @Nullable
+    ImeTracker.Token mCurStatsToken;
+
+    /**
+     * Currently enabled session.
+     */
+    @GuardedBy("ImfLock.class")
+    @Nullable
+    InputMethodManagerService.SessionState mEnabledSession;
+
+    @GuardedBy("ImfLock.class")
+    @Nullable
+    SparseArray<InputMethodManagerService.AccessibilitySessionState>
+            mEnabledAccessibilitySessions = new SparseArray<>();
+
+    /**
+     * A per-user cache of {@link InputMethodSettings#getEnabledInputMethodsStr()}.
+     */
+    @GuardedBy("ImfLock.class")
+    @NonNull
+    String mLastEnabledInputMethodsStr = "";
+
+    /**
+     * {@code true} when the IME is responsible for drawing the navigation bar and its buttons.
+     */
+    @NonNull
+    final AtomicBoolean mImeDrawsNavBar = new AtomicBoolean();
+
+    /**
+     * Intended to be instantiated only from this file.
+     */
+    UserData(@UserIdInt int userId,
+            @NonNull InputMethodBindingController bindingController) {
+        mUserId = userId;
+        mBindingController = bindingController;
+    }
+
+    @Override
+    public String toString() {
+        return "UserData{" + "mUserId=" + mUserId + '}';
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 7c68d54..6f831cc 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -18,18 +18,11 @@
 
 import android.annotation.AnyThread;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.util.SparseArray;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.ImeTracker;
-import android.window.ImeOnBackInvokedDispatcher;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
-import com.android.internal.inputmethod.IRemoteInputConnection;
 
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.function.Consumer;
 import java.util.function.IntFunction;
@@ -87,120 +80,4 @@
             mUserDataLock.writeLock().unlock();
         }
     }
-
-    /** Placeholder for all IMMS user specific fields */
-    static final class UserData {
-        @UserIdInt
-        final int mUserId;
-
-        @NonNull
-        final InputMethodBindingController mBindingController;
-
-        @NonNull
-        final InputMethodSubtypeSwitchingController mSwitchingController;
-
-        @NonNull
-        final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController;
-
-        /**
-         * Have we called mCurMethod.bindInput()?
-         */
-        @GuardedBy("ImfLock.class")
-        boolean mBoundToMethod = false;
-
-        /**
-         * Have we called bindInput() for accessibility services?
-         */
-        @GuardedBy("ImfLock.class")
-        boolean mBoundToAccessibility;
-
-        @GuardedBy("ImfLock.class")
-        @NonNull
-        ImeBindingState mImeBindingState = ImeBindingState.newEmptyState();
-
-        @GuardedBy("ImfLock.class")
-        @Nullable
-        ClientState mCurClient = null;
-
-        @GuardedBy("ImfLock.class")
-        boolean mInFullscreenMode;
-
-        /**
-         * The {@link IRemoteInputConnection} last provided by the current client.
-         */
-        @GuardedBy("ImfLock.class")
-        @Nullable
-        IRemoteInputConnection mCurInputConnection;
-
-        /**
-         * The {@link ImeOnBackInvokedDispatcher} last provided by the current client to
-         * receive {@link android.window.OnBackInvokedCallback}s forwarded from IME.
-         */
-        @GuardedBy("ImfLock.class")
-        @Nullable
-        ImeOnBackInvokedDispatcher mCurImeDispatcher;
-
-        /**
-         * The {@link IRemoteAccessibilityInputConnection} last provided by the current client.
-         */
-        @GuardedBy("ImfLock.class")
-        @Nullable
-        IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection;
-
-        /**
-         * The {@link EditorInfo} last provided by the current client.
-         */
-        @GuardedBy("ImfLock.class")
-        @Nullable
-        EditorInfo mCurEditorInfo;
-
-        /**
-         * The token tracking the current IME show request that is waiting for a connection to an
-         * IME, otherwise {@code null}.
-         */
-        @GuardedBy("ImfLock.class")
-        @Nullable
-        ImeTracker.Token mCurStatsToken;
-
-        /**
-         * Currently enabled session.
-         */
-        @GuardedBy("ImfLock.class")
-        @Nullable
-        InputMethodManagerService.SessionState mEnabledSession;
-
-        @GuardedBy("ImfLock.class")
-        @Nullable
-        SparseArray<InputMethodManagerService.AccessibilitySessionState>
-                mEnabledAccessibilitySessions = new SparseArray<>();
-
-        /**
-         * A per-user cache of {@link InputMethodSettings#getEnabledInputMethodsStr()}.
-         */
-        @GuardedBy("ImfLock.class")
-        @NonNull
-        String mLastEnabledInputMethodsStr = "";
-
-        /**
-         * {@code true} when the IME is responsible for drawing the navigation bar and its buttons.
-         */
-        @NonNull
-        final AtomicBoolean mImeDrawsNavBar = new AtomicBoolean();
-
-        /**
-         * Intended to be instantiated only from this file.
-         */
-        private UserData(@UserIdInt int userId,
-                @NonNull InputMethodBindingController bindingController) {
-            mUserId = userId;
-            mBindingController = bindingController;
-            mSwitchingController = new InputMethodSubtypeSwitchingController();
-            mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController();
-        }
-
-        @Override
-        public String toString() {
-            return "UserData{" + "mUserId=" + mUserId + '}';
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 4851a81..3d0b079 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -48,6 +48,7 @@
 import android.util.Slog;
 import android.util.Xml;
 
+import com.android.internal.annotations.KeepForWeakReference;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.util.FrameworkStatsLog;
@@ -100,6 +101,7 @@
 
     private LocaleManagerBackupHelper mBackupHelper;
 
+    @KeepForWeakReference
     private final PackageMonitor mPackageMonitor;
 
     private final Object mWriteLock = new Object();
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 9e53cc3..016abff 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -110,8 +110,8 @@
 import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
 import static android.service.notification.Flags.callstyleCallbackApi;
 import static android.service.notification.Flags.notificationForceGrouping;
-import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners;
 import static android.service.notification.Flags.redactSensitiveNotificationsBigTextStyle;
+import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
@@ -248,7 +248,6 @@
 import android.content.pm.VersionedPackage;
 import android.content.res.Resources;
 import android.database.ContentObserver;
-import android.graphics.drawable.Icon;
 import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.Binder;
@@ -373,7 +372,6 @@
 import com.android.server.wm.BackgroundActivityStartCallback;
 import com.android.server.wm.WindowManagerInternal;
 
-import java.util.function.BiPredicate;
 import libcore.io.IoUtils;
 
 import org.json.JSONException;
@@ -4634,29 +4632,28 @@
 
         @Override
         public List<String> getPackagesBypassingDnd(int userId,
-                                                    boolean includeConversationChannels) {
+                boolean includeConversationChannels) {
             checkCallerIsSystem();
 
             final ArraySet<String> packageNames = new ArraySet<>();
 
-            for (int user : mUm.getProfileIds(userId, false)) {
-                List<PackageInfo> pkgs = mPackageManagerClient.getInstalledPackagesAsUser(0, user);
-                for (PackageInfo pi : pkgs) {
-                    String pkg = pi.packageName;
-                    // If any NotificationChannel for this package is bypassing, the
-                    // package is considered bypassing.
-                    for (NotificationChannel channel : getNotificationChannelsBypassingDnd(pkg,
-                            pi.applicationInfo.uid).getList()) {
-                        // Skips non-demoted conversation channels.
-                        if (!includeConversationChannels
-                                && !TextUtils.isEmpty(channel.getConversationId())
-                                && !channel.isDemoted()) {
-                            continue;
-                        }
-                        packageNames.add(pkg);
+            List<PackageInfo> pkgs = mPackageManagerClient.getInstalledPackagesAsUser(0, userId);
+            for (PackageInfo pi : pkgs) {
+                String pkg = pi.packageName;
+                // If any NotificationChannel for this package is bypassing, the
+                // package is considered bypassing.
+                for (NotificationChannel channel : getNotificationChannelsBypassingDnd(pkg,
+                        pi.applicationInfo.uid).getList()) {
+                    // Skips non-demoted conversation channels.
+                    if (!includeConversationChannels
+                            && !TextUtils.isEmpty(channel.getConversationId())
+                            && !channel.isDemoted()) {
+                        continue;
                     }
+                    packageNames.add(pkg);
                 }
             }
+
             return new ArrayList<String>(packageNames);
         }
 
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 303371b..8d3f07e 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2261,6 +2261,12 @@
                         installRequest.getNewUsers());
                 mPm.updateSequenceNumberLP(ps, installRequest.getNewUsers());
                 mPm.updateInstantAppInstallerLocked(packageName);
+
+                // The installation is success, remove the split info copy stored in package
+                // setting for the downgrade version check of DELETE_KEEP_DATA and archived app
+                // cases.
+                ps.setSplitNames(null);
+                ps.setSplitRevisionCodes(null);
             }
             installRequest.onCommitFinished();
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index c3cac20..a1dffc6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -1423,11 +1423,8 @@
      */
     public static void checkDowngrade(@NonNull PackageSetting before,
             @NonNull PackageInfoLite after) throws PackageManagerException {
-        if (after.getLongVersionCode() < before.getVersionCode()) {
-            throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
-                    "Update version code " + after.versionCode + " is older than current "
-                            + before.getVersionCode());
-        }
+        checkDowngrade(before.getVersionCode(), before.getBaseRevisionCode(),
+                before.getSplitNames(), before.getSplitRevisionCodes(), after);
     }
 
     /**
@@ -1436,28 +1433,35 @@
      */
     public static void checkDowngrade(@NonNull AndroidPackage before,
             @NonNull PackageInfoLite after) throws PackageManagerException {
-        if (after.getLongVersionCode() < before.getLongVersionCode()) {
+        checkDowngrade(before.getLongVersionCode(), before.getBaseRevisionCode(),
+                before.getSplitNames(), before.getSplitRevisionCodes(), after);
+    }
+
+    private static void checkDowngrade(long beforeVersionCode, int beforeBaseRevisionCode,
+            @NonNull String[] beforeSplitNames, @NonNull int[] beforeSplitRevisionCodes,
+            @NonNull PackageInfoLite after) throws PackageManagerException {
+        if (after.getLongVersionCode() < beforeVersionCode) {
             throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
                     "Update version code " + after.versionCode + " is older than current "
-                            + before.getLongVersionCode());
-        } else if (after.getLongVersionCode() == before.getLongVersionCode()) {
-            if (after.baseRevisionCode < before.getBaseRevisionCode()) {
+                            + beforeVersionCode);
+        } else if (after.getLongVersionCode() == beforeVersionCode) {
+            if (after.baseRevisionCode < beforeBaseRevisionCode) {
                 throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
                         "Update base revision code " + after.baseRevisionCode
-                                + " is older than current " + before.getBaseRevisionCode());
+                                + " is older than current " + beforeBaseRevisionCode);
             }
 
             if (!ArrayUtils.isEmpty(after.splitNames)) {
                 for (int i = 0; i < after.splitNames.length; i++) {
                     final String splitName = after.splitNames[i];
-                    final int j = ArrayUtils.indexOf(before.getSplitNames(), splitName);
+                    final int j = ArrayUtils.indexOf(beforeSplitNames, splitName);
                     if (j != -1) {
-                        if (after.splitRevisionCodes[i] < before.getSplitRevisionCodes()[j]) {
+                        if (after.splitRevisionCodes[i] < beforeSplitRevisionCodes[j]) {
                             throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
                                     "Update split " + splitName + " revision code "
                                             + after.splitRevisionCodes[i]
                                             + " is older than current "
-                                            + before.getSplitRevisionCodes()[j]);
+                                            + beforeSplitRevisionCodes[j]);
                         }
                     }
                 }
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 82df527..9f10e01 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -234,6 +234,22 @@
     @Nullable
     private byte[] mRestrictUpdateHash;
 
+    // This is the copy of the same data stored in AndroidPackage. It is not null if the
+    // AndroidPackage is deleted in cases of DELETE_KEEP_DATA. When AndroidPackage is not null,
+    // the field will be null, and the getter method will return the data from AndroidPackage
+    // instead.
+    @Nullable
+    private String[] mSplitNames;
+
+    // This is the copy of the same data stored in AndroidPackage. It is not null if the
+    // AndroidPackage is deleted in cases of DELETE_KEEP_DATA. When AndroidPackage is not null,
+    // the field will be null, and the getter method will return the data from AndroidPackage
+    // instead.
+    @Nullable
+    private int[] mSplitRevisionCodes;
+
+    private int mBaseRevisionCode;
+
     /**
      * Snapshot support.
      */
@@ -578,6 +594,62 @@
         return getBoolean(Booleans.DEBUGGABLE);
     }
 
+    /**
+     * @see AndroidPackage#getBaseRevisionCode
+     */
+    public PackageSetting setBaseRevisionCode(int value) {
+        mBaseRevisionCode = value;
+        onChanged();
+        return this;
+    }
+
+    /**
+     * @see AndroidPackage#getBaseRevisionCode
+     */
+    public int getBaseRevisionCode() {
+        return mBaseRevisionCode;
+    }
+
+    /**
+     * @see AndroidPackage#getSplitNames
+     */
+    public PackageSetting setSplitNames(String[] value) {
+        mSplitNames = value;
+        onChanged();
+        return this;
+    }
+
+    /**
+     * @see AndroidPackage#getSplitNames
+     */
+    @NonNull
+    public String[] getSplitNames() {
+        if (pkg != null) {
+            return pkg.getSplitNames();
+        }
+        return mSplitNames == null ? EmptyArray.STRING : mSplitNames;
+    }
+
+    /**
+     * @see AndroidPackage#getSplitRevisionCodes
+     */
+    public PackageSetting setSplitRevisionCodes(int[] value) {
+        mSplitRevisionCodes = value;
+        onChanged();
+        return this;
+    }
+
+    /**
+     * @see AndroidPackage#getSplitRevisionCodes
+     */
+    @NonNull
+    public int[] getSplitRevisionCodes() {
+        if (pkg != null) {
+            return pkg.getSplitRevisionCodes();
+        }
+        return mSplitRevisionCodes == null ? EmptyArray.INT : mSplitRevisionCodes;
+    }
+
     @Override
     public String toString() {
         return "PackageSetting{"
@@ -739,6 +811,11 @@
         mTargetSdkVersion = other.mTargetSdkVersion;
         mRestrictUpdateHash = other.mRestrictUpdateHash == null
                 ? null : other.mRestrictUpdateHash.clone();
+        mBaseRevisionCode = other.mBaseRevisionCode;
+        mSplitNames = other.mSplitNames != null
+                ? Arrays.copyOf(other.mSplitNames, other.mSplitNames.length) : null;
+        mSplitRevisionCodes = other.mSplitRevisionCodes != null
+                ? Arrays.copyOf(other.mSplitRevisionCodes, other.mSplitRevisionCodes.length) : null;
 
         usesSdkLibraries = other.usesSdkLibraries != null
                 ? Arrays.copyOf(other.usesSdkLibraries,
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 2f2c451..7afc358 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -432,6 +432,13 @@
                 }
                 deletedPs.setInstalled(/* installed= */ false, userId);
             }
+
+            // Preserve split apk information for downgrade check with DELETE_KEEP_DATA and archived
+            // app cases
+            if (deletedPkg.getSplitNames() != null) {
+                deletedPs.setSplitNames(deletedPkg.getSplitNames());
+                deletedPs.setSplitRevisionCodes(deletedPkg.getSplitRevisionCodes());
+            }
         }
 
         // make sure to preserve per-user installed state if this removal was just
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index d8ce38e..95561f5f 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -437,8 +437,9 @@
             pkgSetting.setIsOrphaned(true);
         }
 
-        // update debuggable to packageSetting
+        // update debuggable and BaseRevisionCode to packageSetting
         pkgSetting.setDebuggable(parsedPackage.isDebuggable());
+        pkgSetting.setBaseRevisionCode(parsedPackage.getBaseRevisionCode());
 
         // Take care of first install / last update times.
         final long scanFileTime = getLastModifiedTime(parsedPackage);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 0d16b00..9177e2b 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -325,6 +325,7 @@
     private static final String TAG_MIME_TYPE = "mime-type";
     private static final String TAG_ARCHIVE_STATE = "archive-state";
     private static final String TAG_ARCHIVE_ACTIVITY_INFO = "archive-activity-info";
+    private static final String TAG_SPLIT_VERSION = "split-version";
 
     public static final String ATTR_NAME = "name";
     public static final String ATTR_PACKAGE = "package";
@@ -3261,6 +3262,9 @@
         if (pkg.isLoading()) {
             serializer.attributeBoolean(null, "isLoading", true);
         }
+        if (pkg.getBaseRevisionCode() != 0) {
+            serializer.attributeInt(null, "baseRevisionCode", pkg.getBaseRevisionCode());
+        }
         serializer.attributeFloat(null, "loadingProgress", pkg.getLoadingProgress());
         serializer.attributeLongHex(null, "loadingCompletedTime", pkg.getLoadingCompletedTime());
 
@@ -3289,7 +3293,12 @@
         writeUpgradeKeySetsLPr(serializer, pkg.getKeySetData());
         writeKeySetAliasesLPr(serializer, pkg.getKeySetData());
         writeMimeGroupLPr(serializer, pkg.getMimeGroups());
-
+        // If getPkg is not NULL, these values are from the getPkg. And these values are preserved
+        // for the downgrade check for DELETE_KEEP_DATA and archived app cases. If the getPkg is
+        // not NULL, we don't need to preserve it.
+        if (pkg.getPkg() == null) {
+            writeSplitVersionsLPr(serializer, pkg.getSplitNames(), pkg.getSplitRevisionCodes());
+        }
         serializer.endTag(null, "package");
     }
 
@@ -4071,6 +4080,7 @@
         byte[] restrictUpdateHash = null;
         boolean isScannedAsStoppedSystemApp = false;
         boolean isSdkLibrary = false;
+        int baseRevisionCode = 0;
         try {
             name = parser.getAttributeValue(null, ATTR_NAME);
             realName = parser.getAttributeValue(null, "realName");
@@ -4116,6 +4126,7 @@
             appMetadataFilePath = parser.getAttributeValue(null, "appMetadataFilePath");
             appMetadataSource = parser.getAttributeInt(null, "appMetadataSource",
                     PackageManager.APP_METADATA_SOURCE_UNKNOWN);
+            baseRevisionCode = parser.getAttributeInt(null, "baseRevisionCode", 0);
 
             isScannedAsStoppedSystemApp = parser.getAttributeBoolean(null,
                 "scannedAsStoppedSystemApp", false);
@@ -4269,6 +4280,7 @@
                     .setAppMetadataFilePath(appMetadataFilePath)
                     .setAppMetadataSource(appMetadataSource)
                     .setTargetSdkVersion(targetSdkVersion)
+                    .setBaseRevisionCode(baseRevisionCode)
                     .setRestrictUpdateHash(restrictUpdateHash)
                     .setScannedAsStoppedSystemApp(isScannedAsStoppedSystemApp);
             // Handle legacy string here for single-user mode
@@ -4374,6 +4386,8 @@
                     readUsesStaticLibLPw(parser, packageSetting);
                 } else if (tagName.equals(TAG_USES_SDK_LIB)) {
                     readUsesSdkLibLPw(parser, packageSetting);
+                } else if (tagName.equals(TAG_SPLIT_VERSION)) {
+                    readSplitVersionsLPw(parser, packageSetting);
                 } else {
                     PackageManagerService.reportSettingsProblem(Log.WARN,
                             "Unknown element under <package>: " + parser.getName());
@@ -4470,6 +4484,37 @@
         }
     }
 
+    private void readSplitVersionsLPw(TypedXmlPullParser parser, PackageSetting outPs)
+            throws IOException, XmlPullParserException {
+        String splitName = parser.getAttributeValue(null, ATTR_NAME);
+        int splitRevision = parser.getAttributeInt(null, ATTR_VERSION, -1);
+        if (splitName != null && splitRevision >= 0) {
+            outPs.setSplitNames(ArrayUtils.appendElement(String.class,
+                    outPs.getSplitNames(), splitName));
+            outPs.setSplitRevisionCodes(ArrayUtils.appendInt(
+                    outPs.getSplitRevisionCodes(), splitRevision));
+        }
+
+        XmlUtils.skipCurrentTag(parser);
+    }
+
+    private void writeSplitVersionsLPr(TypedXmlSerializer serializer, String[] splitNames,
+            int[] splitRevisionCodes) throws IOException {
+        if (ArrayUtils.isEmpty(splitNames) || ArrayUtils.isEmpty(splitRevisionCodes)
+                || splitNames.length != splitRevisionCodes.length) {
+            return;
+        }
+        final int libLength = splitNames.length;
+        for (int i = 0; i < libLength; i++) {
+            final String splitName = splitNames[i];
+            final int splitRevision = splitRevisionCodes[i];
+            serializer.startTag(null, TAG_SPLIT_VERSION);
+            serializer.attribute(null, ATTR_NAME, splitName);
+            serializer.attributeInt(null, ATTR_VERSION, splitRevision);
+            serializer.endTag(null, TAG_SPLIT_VERSION);
+        }
+    }
+
     private void readDisabledComponentsLPw(PackageSetting packageSetting, TypedXmlPullParser parser,
             int userId) throws IOException, XmlPullParserException {
         int outerDepth = parser.getDepth();
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 8dc9756..8419a60 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -933,8 +933,7 @@
         public void onWakeUp() {
             synchronized (mLock) {
                 if (shouldEnableWakeGestureLp()) {
-                    performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, false,
-                            "Wake Up");
+                    performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, "Wake Up");
                     mWindowWakeUpPolicy.wakeUpFromWakeGesture();
                 }
             }
@@ -1403,7 +1402,7 @@
                 break;
             case LONG_PRESS_POWER_GLOBAL_ACTIONS:
                 mPowerKeyHandled = true;
-                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
+                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON,
                         "Power - Long Press - Global Actions");
                 showGlobalActions();
                 break;
@@ -1415,14 +1414,14 @@
                 if (ActivityManager.isUserAMonkey()) {
                     break;
                 }
-                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
+                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON,
                         "Power - Long Press - Shut Off");
                 sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
                 mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
                 break;
             case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST:
                 mPowerKeyHandled = true;
-                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
+                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON,
                         "Power - Long Press - Go To Voice Assist");
                 // Some devices allow the voice assistant intent during setup (and use that intent
                 // to launch something else, like Settings). So we explicitly allow that via the
@@ -1431,7 +1430,7 @@
                 break;
             case LONG_PRESS_POWER_ASSISTANT:
                 mPowerKeyHandled = true;
-                performHapticFeedback(HapticFeedbackConstants.ASSISTANT_BUTTON, false,
+                performHapticFeedback(HapticFeedbackConstants.ASSISTANT_BUTTON,
                         "Power - Long Press - Go To Assistant");
                 final int powerKeyDeviceId = INVALID_INPUT_DEVICE_ID;
                 launchAssistAction(null, powerKeyDeviceId, eventTime,
@@ -1446,7 +1445,7 @@
                 break;
             case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS:
                 mPowerKeyHandled = true;
-                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
+                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON,
                         "Power - Very Long Press - Show Global Actions");
                 showGlobalActions();
                 break;
@@ -1599,8 +1598,7 @@
             case TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY:
                 mTalkbackShortcutController.toggleTalkback(mCurrentUserId);
                 if (mTalkbackShortcutController.isTalkBackShortcutGestureEnabled()) {
-                    performHapticFeedback(HapticFeedbackConstants.CONFIRM, /* always = */
-                            false, /* reason = */
+                    performHapticFeedback(HapticFeedbackConstants.CONFIRM,
                             "Stem primary - Triple Press - Toggle Accessibility");
                 }
                 break;
@@ -1771,7 +1769,7 @@
         @Override
         public void run() {
             mEndCallKeyHandled = true;
-            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
+            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
                     "End Call - Long Press - Show Global Actions");
             showGlobalActionsInternal();
         }
@@ -2087,8 +2085,7 @@
                 return;
             }
             mHomeConsumed = true;
-            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
-                    "Home - Long Press");
+            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, "Home - Long Press");
             switch (mLongPressOnHomeBehavior) {
                 case LONG_PRESS_HOME_ALL_APPS:
                     if (mHasFeatureLeanback) {
@@ -2530,7 +2527,7 @@
                                 break;
                             case POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS:
                                 performHapticFeedback(
-                                        HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
+                                        HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON,
                                         "Power + Volume Up - Global Actions");
                                 showGlobalActions();
                                 mPowerKeyHandled = true;
@@ -5078,8 +5075,7 @@
         }
 
         if (useHapticFeedback) {
-            performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, false,
-                    "Virtual Key - Press");
+            performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, "Virtual Key - Press");
         }
 
         if (isWakeKey) {
@@ -5971,8 +5967,10 @@
     public void setSafeMode(boolean safeMode) {
         mSafeMode = safeMode;
         if (safeMode) {
-            performHapticFeedback(HapticFeedbackConstants.SAFE_MODE_ENABLED, true,
-                    "Safe Mode Enabled");
+            performHapticFeedback(Process.myUid(), mContext.getOpPackageName(),
+                    HapticFeedbackConstants.SAFE_MODE_ENABLED,
+                    "Safe Mode Enabled", HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
+                    0 /* privFlags */);
         }
     }
 
@@ -6441,9 +6439,9 @@
                 Settings.Global.THEATER_MODE_ON, 0) == 1;
     }
 
-    private boolean performHapticFeedback(int effectId, boolean always, String reason) {
+    private boolean performHapticFeedback(int effectId, String reason) {
         return performHapticFeedback(Process.myUid(), mContext.getOpPackageName(),
-            effectId, always, reason, false /* fromIme */);
+            effectId, reason, 0 /* flags */, 0 /* privFlags */);
     }
 
     @Override
@@ -6452,8 +6450,8 @@
     }
 
     @Override
-    public boolean performHapticFeedback(int uid, String packageName, int effectId,
-            boolean always, String reason, boolean fromIme) {
+    public boolean performHapticFeedback(int uid, String packageName, int effectId, String reason,
+            int flags, int privFlags) {
         if (!mVibrator.hasVibrator()) {
             return false;
         }
@@ -6464,7 +6462,7 @@
         }
         VibrationAttributes attrs =
                 mHapticFeedbackVibrationProvider.getVibrationAttributesForHapticFeedback(
-                        effectId, /* bypassVibrationIntensitySetting= */ always, fromIme);
+                        effectId, flags, privFlags);
         VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, effectId);
         mVibrator.vibrate(uid, packageName, effect, reason, attrs);
         return true;
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 6c05d70..1b394f6 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -80,6 +80,7 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
+import android.view.HapticFeedbackConstants;
 import android.view.IDisplayFoldListener;
 import android.view.KeyEvent;
 import android.view.KeyboardShortcutGroup;
@@ -1079,7 +1080,8 @@
      * Call from application to perform haptic feedback on its window.
      */
     public boolean performHapticFeedback(int uid, String packageName, int effectId,
-            boolean always, String reason, boolean fromIme);
+            String reason, @HapticFeedbackConstants.Flags int flags,
+            @HapticFeedbackConstants.PrivateFlags int privFlags);
 
     /**
      * Called when we have started keeping the screen on because a window
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index 8138168..98a2ba0d 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -186,13 +186,13 @@
      *
      * @param effectId the haptic feedback effect ID whose respective vibration attributes we want
      *      to get.
-     * @param bypassVibrationIntensitySetting {@code true} if the returned attribute should bypass
-     *      vibration intensity settings. {@code false} otherwise.
-     * @param fromIme the haptic feedback is performed from an IME.
+     * @param flags Additional flags as per {@link HapticFeedbackConstants}.
+     * @param privFlags Additional private flags as per {@link HapticFeedbackConstants}.
      * @return the {@link VibrationAttributes} that should be used for the provided haptic feedback.
      */
-    public VibrationAttributes getVibrationAttributesForHapticFeedback(
-            int effectId, boolean bypassVibrationIntensitySetting, boolean fromIme) {
+    public VibrationAttributes getVibrationAttributesForHapticFeedback(int effectId,
+            @HapticFeedbackConstants.Flags int flags,
+            @HapticFeedbackConstants.PrivateFlags int privFlags) {
         VibrationAttributes attrs;
         switch (effectId) {
             case HapticFeedbackConstants.EDGE_SQUEEZE:
@@ -208,7 +208,7 @@
                 break;
             case HapticFeedbackConstants.KEYBOARD_TAP:
             case HapticFeedbackConstants.KEYBOARD_RELEASE:
-                attrs = createKeyboardVibrationAttributes(fromIme);
+                attrs = createKeyboardVibrationAttributes(privFlags);
                 break;
             case HapticFeedbackConstants.BIOMETRIC_CONFIRM:
             case HapticFeedbackConstants.BIOMETRIC_REJECT:
@@ -218,18 +218,23 @@
                 attrs = TOUCH_VIBRATION_ATTRIBUTES;
         }
 
-        int flags = 0;
+        int vibFlags = 0;
+        boolean fromIme =
+                (privFlags & HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS) != 0;
+        boolean bypassVibrationIntensitySetting =
+                (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0;
         if (bypassVibrationIntensitySetting) {
-            flags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
+            vibFlags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
         }
         if (shouldBypassInterruptionPolicy(effectId)) {
-            flags |= VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
+            vibFlags |= VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
         }
         if (shouldBypassIntensityScale(effectId, fromIme)) {
-            flags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
+            vibFlags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
         }
 
-        return flags == 0 ? attrs : new VibrationAttributes.Builder(attrs).setFlags(flags).build();
+        return vibFlags == 0 ? attrs : new VibrationAttributes.Builder(attrs)
+                .setFlags(vibFlags).build();
     }
 
     /**
@@ -373,12 +378,16 @@
         return false;
     }
 
-    private VibrationAttributes createKeyboardVibrationAttributes(boolean fromIme) {
-        // Use touch attribute when the keyboard category is disable or it's not from an IME.
-        if (!Flags.keyboardCategoryEnabled() || !fromIme) {
+    private VibrationAttributes createKeyboardVibrationAttributes(
+            @HapticFeedbackConstants.PrivateFlags int privFlags) {
+        // Use touch attribute when the keyboard category is disable.
+        if (!Flags.keyboardCategoryEnabled()) {
             return TOUCH_VIBRATION_ATTRIBUTES;
         }
-
+        // Use touch attribute when the haptic is not apply to IME.
+        if ((privFlags & HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS) == 0) {
+            return TOUCH_VIBRATION_ATTRIBUTES;
+        }
         return new VibrationAttributes.Builder(TOUCH_VIBRATION_ATTRIBUTES)
                 .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
                 .build();
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 5c15ccb..4437a2d 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -65,6 +65,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
+import android.view.HapticFeedbackConstants;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -439,13 +440,13 @@
 
     @Override // Binder call
     public void performHapticFeedback(int uid, int deviceId, String opPkg, int constant,
-            boolean always, String reason, boolean fromIme) {
+            String reason, int flags, int privFlags) {
         // Note that the `performHapticFeedback` method does not take a token argument from the
         // caller, and instead, uses this service as the token. This is to mitigate performance
         // impact that would otherwise be caused due to marshal latency. Haptic feedback effects are
         // short-lived, so we don't need to cancel when the process dies.
-        performHapticFeedbackInternal(
-                uid, deviceId, opPkg, constant, always, reason, /* token= */ this, fromIme);
+        performHapticFeedbackInternal(uid, deviceId, opPkg, constant, reason, /* token= */
+                this, flags, privFlags);
     }
 
     /**
@@ -456,8 +457,8 @@
     @VisibleForTesting
     @Nullable
     HalVibration performHapticFeedbackInternal(
-            int uid, int deviceId, String opPkg, int constant, boolean always, String reason,
-            IBinder token, boolean fromIme) {
+            int uid, int deviceId, String opPkg, int constant, String reason,
+            IBinder token, int flags, int privFlags) {
         HapticFeedbackVibrationProvider hapticVibrationProvider = getHapticVibrationProvider();
         if (hapticVibrationProvider == null) {
             Slog.e(TAG, "performHapticFeedback; haptic vibration provider not ready.");
@@ -474,9 +475,8 @@
             return null;
         }
         CombinedVibration vib = CombinedVibration.createParallel(effect);
-        VibrationAttributes attrs =
-                hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
-                        constant, /* bypassVibrationIntensitySetting= */ always, fromIme);
+        VibrationAttributes attrs = hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
+                constant, flags, privFlags);
         reason = "performHapticFeedback(constant=" + constant + "): " + reason;
         VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, constant);
         return vibrateWithoutPermissionCheck(uid, deviceId, opPkg, vib, attrs, reason, token);
@@ -2295,10 +2295,11 @@
 
             IBinder deathBinder = commonOptions.background ? VibratorManagerService.this
                     : mShellCallbacksToken;
+            int flags = commonOptions.force
+                    ? HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING : 0;
             HalVibration vib = performHapticFeedbackInternal(Binder.getCallingUid(),
                     Context.DEVICE_ID_DEFAULT, SHELL_PACKAGE_NAME, constant,
-                    /* always= */ commonOptions.force, /* reason= */ commonOptions.description,
-                    deathBinder, false /* fromIme */);
+                    /* reason= */ commonOptions.description, deathBinder, flags, /* privFlags */ 0);
             maybeWaitOnVibration(vib, commonOptions);
 
             return 0;
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index 50ac801..66653ca 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -164,17 +164,16 @@
     private void calculateAndCentreInitialBounds(Task task,
             LaunchParamsController.LaunchParams outParams) {
         // TODO(b/319819547): Account for app constraints so apps do not become letterboxed
-        final Rect stableBounds = new Rect();
-        task.getDisplayArea().getStableRect(stableBounds);
+        final Rect screenBounds = task.getDisplayArea().getBounds();
         // The desired dimensions that a fully resizable window should take when initially entering
         // desktop mode. Calculated as a percentage of the available display area as defined by the
         // DESKTOP_MODE_INITIAL_BOUNDS_SCALE.
-        final int desiredWidth = (int) (stableBounds.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
-        final int desiredHeight = (int) (stableBounds.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth = (int) (screenBounds.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight = (int) (screenBounds.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
         outParams.mBounds.right = desiredWidth;
         outParams.mBounds.bottom = desiredHeight;
-        outParams.mBounds.offset(stableBounds.centerX() - outParams.mBounds.centerX(),
-                stableBounds.centerY() - outParams.mBounds.centerY());
+        outParams.mBounds.offset(screenBounds.centerX() - outParams.mBounds.centerX(),
+                screenBounds.centerY() - outParams.mBounds.centerY());
     }
 
     private void initLogBuilder(Task task, ActivityRecord activity) {
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index bf99ccd..d79c11ce 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -408,9 +408,8 @@
             final boolean intersectsTopCutout = topDisplayCutout.intersects(
                     width - (windowWidth / 2), 0,
                     width + (windowWidth / 2), topDisplayCutout.bottom);
-            if (mClingWindow != null &&
-                    (windowWidth < 0 || (width > 0 && intersectsTopCutout))) {
-                final View iconView = mClingWindow.findViewById(R.id.immersive_cling_icon);
+            if (windowWidth < 0 || (width > 0 && intersectsTopCutout)) {
+                final View iconView = findViewById(R.id.immersive_cling_icon);
                 RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)
                         iconView.getLayoutParams();
                 lp.topMargin = topDisplayCutout.bottom;
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index f5108f5b..c26684f 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -324,19 +324,19 @@
     }
 
     @Override
-    public boolean performHapticFeedback(int effectId, boolean always, boolean fromIme) {
+    public boolean performHapticFeedback(int effectId, int flags, int privFlags) {
         final long ident = Binder.clearCallingIdentity();
         try {
-            return mService.mPolicy.performHapticFeedback(mUid, mPackageName,
-                        effectId, always, null, fromIme);
+            return mService.mPolicy.performHapticFeedback(mUid, mPackageName, effectId, null, flags,
+                    privFlags);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override
-    public void performHapticFeedbackAsync(int effectId, boolean always, boolean fromIme) {
-        performHapticFeedback(effectId, always, fromIme);
+    public void performHapticFeedbackAsync(int effectId, int flags, int privFlags) {
+        performHapticFeedback(effectId, flags, privFlags);
     }
 
     /* Drag/drop */
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 5336044..f6a68d5 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.ActivityOptions.ANIM_CUSTOM;
 import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
@@ -1897,7 +1898,8 @@
         }
     }
 
-    private void overrideAnimationOptionsToInfoIfNecessary(@NonNull TransitionInfo info) {
+    @VisibleForTesting
+    void overrideAnimationOptionsToInfoIfNecessary(@NonNull TransitionInfo info) {
         if (mOverrideOptions == null) {
             return;
         }
@@ -1914,12 +1916,28 @@
                     changes.get(i).setAnimationOptions(mOverrideOptions);
                     // TODO(b/295805497): Extract mBackgroundColor from AnimationOptions.
                     changes.get(i).setBackgroundColor(mOverrideOptions.getBackgroundColor());
+                } else if (shouldApplyAnimOptionsToEmbeddedTf(container.asTaskFragment())) {
+                    // We only override AnimationOptions because backgroundColor should be from
+                    // TaskFragmentAnimationParams.
+                    changes.get(i).setAnimationOptions(mOverrideOptions);
                 }
             }
         }
         updateActivityTargetForCrossProfileAnimation(info);
     }
 
+    private boolean shouldApplyAnimOptionsToEmbeddedTf(@Nullable TaskFragment taskFragment) {
+        if (taskFragment == null || !taskFragment.isEmbedded()) {
+            return false;
+        }
+        if (taskFragment.getAnimationParams().hasOverrideAnimation()) {
+            // Always respect animation overrides from TaskFragmentAnimationParams.
+            return false;
+        }
+        // ActivityEmbedding animation adapter only support custom animation
+        return mOverrideOptions != null && mOverrideOptions.getType() == ANIM_CUSTOM;
+    }
+
     /**
      * Updates activity open target if {@link #mOverrideOptions} is
      * {@link ANIM_OPEN_CROSS_PROFILE_APPS}.
@@ -1929,8 +1947,7 @@
             return;
         }
         for (int i = 0; i < mTargets.size(); ++i) {
-            final ActivityRecord activity = mTargets.get(i).mContainer
-                    .asActivityRecord();
+            final ActivityRecord activity = mTargets.get(i).mContainer.asActivityRecord();
             final TransitionInfo.Change change = info.getChanges().get(i);
             if (activity == null || change.getMode() != TRANSIT_OPEN) {
                 continue;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e122fe0..032d6b5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1131,9 +1131,9 @@
         }
 
         @Override
-        public void onUserUnlocked(@NonNull TargetUser user) {
-            if (user.isPreCreated()) return;
-            mService.handleOnUserUnlocked(user.getUserIdentifier());
+        public void onUserSwitching(@NonNull TargetUser from, @NonNull TargetUser to) {
+            if (to.isPreCreated()) return;
+            mService.handleOnUserSwitching(from.getUserIdentifier(), to.getUserIdentifier());
         }
     }
 
@@ -3831,8 +3831,8 @@
         mDevicePolicyEngine.handleUnlockUser(userId);
     }
 
-    void handleOnUserUnlocked(int userId) {
-        showNewUserDisclaimerIfNecessary(userId);
+    void handleOnUserSwitching(int fromUserId, int toUserId) {
+        showNewUserDisclaimerIfNecessary(toUserId);
     }
 
     void handleStopUser(int userId) {
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
index 81fb1a0..d59f28b 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
@@ -98,8 +98,8 @@
         assertThat(allUserData.get(0).mBindingController.getUserId()).isEqualTo(ANY_USER_ID);
     }
 
-    private List<UserDataRepository.UserData> collectUserData(UserDataRepository repository) {
-        final var collected = new ArrayList<UserDataRepository.UserData>();
+    private List<UserData> collectUserData(UserDataRepository repository) {
+        final var collected = new ArrayList<UserData>();
         repository.forAllUserData(userData -> collected.add(userData));
         return collected;
     }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 7138306..dec4634 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -1046,6 +1046,77 @@
     }
 
     @Test
+    public void testWriteReadBaseRevisionCode() {
+        Settings settings = makeSettings();
+        PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+        packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+        packageSetting.setPkg(PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed()
+                .setUid(packageSetting.getAppId())
+                .hideAsFinal());
+
+        final int revisionCode = 311;
+        packageSetting.setBaseRevisionCode(revisionCode);
+        settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+        settings.writeLPr(computer, /* sync= */ true);
+        settings.mPackages.clear();
+
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        assertThat(settings.getPackageLPr(PACKAGE_NAME_1).getBaseRevisionCode(), is(revisionCode));
+    }
+
+    @Test
+    public void testHasPkg_writeReadSplitVersions() {
+        Settings settings = makeSettings();
+        PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+        packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+        packageSetting.setPkg(PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed()
+                .setUid(packageSetting.getAppId())
+                .hideAsFinal());
+
+        final String splitOne = "one";
+        final String splitTwo = "two";
+        final int revisionOne = 311;
+        final int revisionTwo = 330;
+        packageSetting.setSplitNames(new String[] { splitOne, splitTwo});
+        packageSetting.setSplitRevisionCodes(new int[] { revisionOne, revisionTwo});
+        settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+        settings.writeLPr(computer, /* sync= */ true);
+        settings.mPackages.clear();
+
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+        assertThat(resultSetting.getSplitNames().length, is(0));
+        assertThat(resultSetting.getSplitRevisionCodes().length, is(0));
+    }
+
+    @Test
+    public void testNoPkg_writeReadSplitVersions() {
+        Settings settings = makeSettings();
+        PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+        packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+
+        final String splitOne = "one";
+        final String splitTwo = "two";
+        final int revisionOne = 311;
+        final int revisionTwo = 330;
+        packageSetting.setSplitNames(new String[] { splitOne, splitTwo});
+        packageSetting.setSplitRevisionCodes(new int[] { revisionOne, revisionTwo});
+        settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+        settings.writeLPr(computer, /* sync= */ true);
+        settings.mPackages.clear();
+
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+        assertThat(resultSetting.getSplitNames()[0], is(splitOne));
+        assertThat(resultSetting.getSplitNames()[1], is(splitTwo));
+        assertThat(resultSetting.getSplitRevisionCodes()[0], is(revisionOne));
+        assertThat(resultSetting.getSplitRevisionCodes()[1], is(revisionTwo));
+    }
+
+    @Test
     public void testWriteReadArchiveState() {
         Settings settings = makeSettings();
         PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 753db12..b9e99dd 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -36,6 +36,8 @@
         "-Werror",
     ],
     static_libs: [
+        "a11ychecker-protos-java-proto-lite",
+        "aatf",
         "cts-input-lib",
         "frameworks-base-testutils",
         "services.accessibility",
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
new file mode 100644
index 0000000..90d4275
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 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.accessibility.a11ychecker;
+
+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_A11Y_SERVICE_SOURCE_VERSION_CODE;
+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.TEST_APP_VERSION_CODE;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_WINDOW_TITLE;
+import static com.android.server.accessibility.a11ychecker.TestUtils.getMockPackageManagerWithInstalledApps;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckPreset;
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult;
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheck;
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheckResult;
+import com.google.android.apps.common.testing.accessibility.framework.checks.ClassNameCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.ClickableSpanCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.SpeakableTextPresentCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.TouchTargetSizeCheck;
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityCheckerUtilsTest {
+
+    PackageManager mMockPackageManager;
+
+    @Before
+    public void setUp() throws PackageManager.NameNotFoundException {
+        mMockPackageManager = getMockPackageManagerWithInstalledApps();
+    }
+
+    @Test
+    public void processResults_happyPath_setsAllFields() {
+        AccessibilityNodeInfo mockNodeInfo =
+                new MockAccessibilityNodeInfoBuilder()
+                        .setViewIdResourceName("TargetNode")
+                        .build();
+        AccessibilityHierarchyCheckResult result1 =
+                new AccessibilityHierarchyCheckResult(
+                        SpeakableTextPresentCheck.class,
+                        AccessibilityCheckResult.AccessibilityCheckResultType.WARNING, null, 1,
+                        null);
+        AccessibilityHierarchyCheckResult result2 =
+                new AccessibilityHierarchyCheckResult(
+                        TouchTargetSizeCheck.class,
+                        AccessibilityCheckResult.AccessibilityCheckResultType.ERROR, null, 2, null);
+        AccessibilityHierarchyCheckResult result3 =
+                new AccessibilityHierarchyCheckResult(
+                        ClassNameCheck.class,
+                        AccessibilityCheckResult.AccessibilityCheckResultType.INFO, null, 5, null);
+        AccessibilityHierarchyCheckResult result4 =
+                new AccessibilityHierarchyCheckResult(
+                        ClickableSpanCheck.class,
+                        AccessibilityCheckResult.AccessibilityCheckResultType.NOT_RUN, null, 5,
+                        null);
+
+        Set<A11yCheckerProto.AccessibilityCheckResultReported> atoms =
+                AccessibilityCheckerUtils.processResults(
+                        mockNodeInfo,
+                        List.of(result1, result2, result3, result4),
+                        null,
+                        mMockPackageManager,
+                        new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
+                                TEST_A11Y_SERVICE_CLASS_NAME));
+
+        assertThat(atoms).containsExactly(
+                createAtom(A11yCheckerProto.AccessibilityCheckClass.SPEAKABLE_TEXT_PRESENT_CHECK,
+                        A11yCheckerProto.AccessibilityCheckResultType.WARNING, 1),
+                createAtom(A11yCheckerProto.AccessibilityCheckClass.TOUCH_TARGET_SIZE_CHECK,
+                        A11yCheckerProto.AccessibilityCheckResultType.ERROR, 2)
+        );
+    }
+
+    @Test
+    public void processResults_packageNameNotFound_returnsEmptySet()
+            throws PackageManager.NameNotFoundException {
+        when(mMockPackageManager.getPackageInfo("com.uninstalled.app", 0))
+                .thenThrow(PackageManager.NameNotFoundException.class);
+        AccessibilityNodeInfo mockNodeInfo =
+                new MockAccessibilityNodeInfoBuilder()
+                        .setPackageName("com.uninstalled.app")
+                        .setViewIdResourceName("TargetNode")
+                        .build();
+        AccessibilityHierarchyCheckResult result1 =
+                new AccessibilityHierarchyCheckResult(
+                        TouchTargetSizeCheck.class,
+                        AccessibilityCheckResult.AccessibilityCheckResultType.WARNING, null, 1,
+                        null);
+        AccessibilityHierarchyCheckResult result2 =
+                new AccessibilityHierarchyCheckResult(
+                        TouchTargetSizeCheck.class,
+                        AccessibilityCheckResult.AccessibilityCheckResultType.ERROR, null, 2, null);
+
+        Set<A11yCheckerProto.AccessibilityCheckResultReported> atoms =
+                AccessibilityCheckerUtils.processResults(
+                        mockNodeInfo,
+                        List.of(result1, result2),
+                        null,
+                        mMockPackageManager,
+                        new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
+                                TEST_A11Y_SERVICE_CLASS_NAME));
+
+        assertThat(atoms).isEmpty();
+    }
+
+    @Test
+    public void getActivityName_hasWindowStateChangedEvent_returnsActivityName() {
+        AccessibilityEvent accessibilityEvent =
+                AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+        accessibilityEvent.setPackageName(TEST_APP_PACKAGE_NAME);
+        accessibilityEvent.setClassName(TEST_ACTIVITY_NAME);
+
+        assertThat(AccessibilityCheckerUtils.getActivityName(mMockPackageManager,
+                accessibilityEvent)).isEqualTo("MainActivity");
+    }
+
+    // Makes sure the AccessibilityHierarchyCheck class to enum mapping is up to date with the
+    // latest prod preset.
+    @Test
+    public void checkClassToEnumMap_hasAllLatestPreset() {
+        ImmutableSet<AccessibilityHierarchyCheck> checkPreset =
+                AccessibilityCheckPreset.getAccessibilityHierarchyChecksForPreset(
+                        AccessibilityCheckPreset.LATEST);
+        Set<Class<? extends AccessibilityHierarchyCheck>> latestCheckClasses =
+                checkPreset.stream().map(AccessibilityHierarchyCheck::getClass).collect(
+                        Collectors.toUnmodifiableSet());
+
+        assertThat(AccessibilityCheckerUtils.CHECK_CLASS_TO_ENUM_MAP.keySet())
+                .containsExactlyElementsIn(latestCheckClasses);
+    }
+
+
+    private static A11yCheckerProto.AccessibilityCheckResultReported createAtom(
+            A11yCheckerProto.AccessibilityCheckClass checkClass,
+            A11yCheckerProto.AccessibilityCheckResultType resultType,
+            int resultId) {
+        return A11yCheckerProto.AccessibilityCheckResultReported.newBuilder()
+                .setPackageName(TEST_APP_PACKAGE_NAME)
+                .setAppVersionCode(TEST_APP_VERSION_CODE)
+                .setUiElementPath(TEST_APP_PACKAGE_NAME + ":TargetNode")
+                .setWindowTitle(TEST_WINDOW_TITLE)
+                .setActivityName("")
+                .setSourceComponentName(new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
+                        TEST_A11Y_SERVICE_CLASS_NAME).flattenToString())
+                .setSourceVersionCode(TEST_A11Y_SERVICE_SOURCE_VERSION_CODE)
+                .setResultCheckClass(checkClass)
+                .setResultType(resultType)
+                .setResultId(resultId)
+                .build();
+    }
+
+}
diff --git a/core/tests/coretests/src/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java
similarity index 80%
rename from core/tests/coretests/src/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java
rename to services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java
index 438277b..a53f42e 100644
--- a/core/tests/coretests/src/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package android.view.accessibility.a11ychecker;
+package com.android.server.accessibility.a11ychecker;
 
-import static android.view.accessibility.a11ychecker.MockAccessibilityNodeInfoBuilder.PACKAGE_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_APP_PACKAGE_NAME;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -36,7 +36,7 @@
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityNodePathBuilderTest {
 
-    public static final String RESOURCE_ID_PREFIX = PACKAGE_NAME + ":id/";
+    public static final String RESOURCE_ID_PREFIX = TEST_APP_PACKAGE_NAME + ":id/";
 
     @Test
     public void createNodePath_pathWithResourceNames() {
@@ -55,11 +55,11 @@
                         .build();
 
         assertThat(AccessibilityNodePathBuilder.createNodePath(child))
-                .isEqualTo(PACKAGE_NAME + ":root_node/parent_node[1]/child_node[1]");
+                .isEqualTo(TEST_APP_PACKAGE_NAME + ":root_node/parent_node[1]/child_node[1]");
         assertThat(AccessibilityNodePathBuilder.createNodePath(parent))
-                .isEqualTo(PACKAGE_NAME + ":root_node/parent_node[1]");
+                .isEqualTo(TEST_APP_PACKAGE_NAME + ":root_node/parent_node[1]");
         assertThat(AccessibilityNodePathBuilder.createNodePath(root))
-                .isEqualTo(PACKAGE_NAME + ":root_node");
+                .isEqualTo(TEST_APP_PACKAGE_NAME + ":root_node");
     }
 
     @Test
@@ -81,11 +81,11 @@
                         .build();
 
         assertThat(AccessibilityNodePathBuilder.createNodePath(child))
-                .isEqualTo(PACKAGE_NAME + ":FrameLayout/RecyclerView[1]/TextView[1]");
+                .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout/RecyclerView[1]/TextView[1]");
         assertThat(AccessibilityNodePathBuilder.createNodePath(parent))
-                .isEqualTo(PACKAGE_NAME + ":FrameLayout/RecyclerView[1]");
+                .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout/RecyclerView[1]");
         assertThat(AccessibilityNodePathBuilder.createNodePath(root))
-                .isEqualTo(PACKAGE_NAME + ":FrameLayout");
+                .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout");
     }
 
     @Test
@@ -105,11 +105,11 @@
                         .build();
 
         assertThat(AccessibilityNodePathBuilder.createNodePath(child1))
-                .isEqualTo(PACKAGE_NAME + ":FrameLayout/child1[1]");
+                .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout/child1[1]");
         assertThat(AccessibilityNodePathBuilder.createNodePath(child2))
-                .isEqualTo(PACKAGE_NAME + ":FrameLayout/TextView[2]");
+                .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout/TextView[2]");
         assertThat(AccessibilityNodePathBuilder.createNodePath(parent))
-                .isEqualTo(PACKAGE_NAME + ":FrameLayout");
+                .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout");
     }
 
     @Test
@@ -133,13 +133,13 @@
                         .build();
 
         assertThat(AccessibilityNodePathBuilder.createNodePath(child1))
-                .isEqualTo(PACKAGE_NAME + ":parentId/childId[1]");
+                .isEqualTo(TEST_APP_PACKAGE_NAME + ":parentId/childId[1]");
         assertThat(AccessibilityNodePathBuilder.createNodePath(child2))
-                .isEqualTo(PACKAGE_NAME + ":parentId/child/Id/With/Slash[2]");
+                .isEqualTo(TEST_APP_PACKAGE_NAME + ":parentId/child/Id/With/Slash[2]");
         assertThat(AccessibilityNodePathBuilder.createNodePath(child3))
-                .isEqualTo(PACKAGE_NAME + ":parentId/childIdWithoutPrefix[3]");
+                .isEqualTo(TEST_APP_PACKAGE_NAME + ":parentId/childIdWithoutPrefix[3]");
         assertThat(AccessibilityNodePathBuilder.createNodePath(parent))
-                .isEqualTo(PACKAGE_NAME + ":parentId");
+                .isEqualTo(TEST_APP_PACKAGE_NAME + ":parentId");
     }
 
 }
diff --git a/core/tests/coretests/src/android/view/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java
similarity index 72%
rename from core/tests/coretests/src/android/view/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java
rename to services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java
index e363f0c..7cd3535 100644
--- a/core/tests/coretests/src/android/view/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java
@@ -14,21 +14,33 @@
  * limitations under the License.
  */
 
-package android.view.accessibility.a11ychecker;
+package com.android.server.accessibility.a11ychecker;
+
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_APP_PACKAGE_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_WINDOW_TITLE;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
 
 import java.util.List;
 
 final class MockAccessibilityNodeInfoBuilder {
-    static final String PACKAGE_NAME = "com.example.app";
     private final AccessibilityNodeInfo mMockNodeInfo = mock(AccessibilityNodeInfo.class);
 
     MockAccessibilityNodeInfoBuilder() {
-        when(mMockNodeInfo.getPackageName()).thenReturn(PACKAGE_NAME);
+        setPackageName(TEST_APP_PACKAGE_NAME);
+
+        AccessibilityWindowInfo windowInfo = new AccessibilityWindowInfo();
+        windowInfo.setTitle(TEST_WINDOW_TITLE);
+        when(mMockNodeInfo.getWindow()).thenReturn(windowInfo);
+    }
+
+    MockAccessibilityNodeInfoBuilder setPackageName(String packageName) {
+        when(mMockNodeInfo.getPackageName()).thenReturn(packageName);
+        return this;
     }
 
     MockAccessibilityNodeInfoBuilder setClassName(String className) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/OWNERS b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/OWNERS
new file mode 100644
index 0000000..7bdc029
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/OWNERS
@@ -0,0 +1 @@
+include /services/accessibility/java/com/android/server/accessibility/a11ychecker/OWNERS
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
new file mode 100644
index 0000000..a04bbee
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 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.accessibility.a11ychecker;
+
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+
+import org.mockito.Mockito;
+
+public class TestUtils {
+    static final String TEST_APP_PACKAGE_NAME = "com.example.app";
+    static final int TEST_APP_VERSION_CODE = 12321;
+    static final String TEST_ACTIVITY_NAME = "com.example.app.MainActivity";
+    static final String TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME = "com.assistive.app";
+    static final String TEST_A11Y_SERVICE_CLASS_NAME = "MyA11yService";
+    static final int TEST_A11Y_SERVICE_SOURCE_VERSION_CODE = 333555;
+    static final String TEST_WINDOW_TITLE = "Example window";
+
+    static PackageManager getMockPackageManagerWithInstalledApps()
+            throws PackageManager.NameNotFoundException {
+        PackageManager mockPackageManager = Mockito.mock(PackageManager.class);
+        ActivityInfo testActivityInfo = getTestActivityInfo();
+        ComponentName testActivityComponentName = new ComponentName(TEST_APP_PACKAGE_NAME,
+                TEST_ACTIVITY_NAME);
+
+        when(mockPackageManager.getActivityInfo(testActivityComponentName, 0))
+                .thenReturn(testActivityInfo);
+        when(mockPackageManager.getPackageInfo(TEST_APP_PACKAGE_NAME, 0))
+                .thenReturn(createPackageInfo(TEST_APP_PACKAGE_NAME, TEST_APP_VERSION_CODE,
+                        testActivityInfo));
+        when(mockPackageManager.getPackageInfo(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME, 0))
+                .thenReturn(createPackageInfo(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
+                        TEST_A11Y_SERVICE_SOURCE_VERSION_CODE, null));
+        return mockPackageManager;
+    }
+
+    static ActivityInfo getTestActivityInfo() {
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.packageName = TEST_APP_PACKAGE_NAME;
+        activityInfo.name = TEST_ACTIVITY_NAME;
+        return activityInfo;
+    }
+
+    static PackageInfo createPackageInfo(String packageName, int versionCode,
+            @Nullable ActivityInfo activityInfo) {
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = packageName;
+        packageInfo.setLongVersionCode(versionCode);
+        if (activityInfo != null) {
+            packageInfo.activities = new ActivityInfo[]{activityInfo};
+        }
+        return packageInfo;
+
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
index ef9580c..758c84a 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
@@ -101,7 +101,7 @@
         mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
                 mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy,
                 mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
-                mock(AudioServerPermissionProvider.class), r -> r.run()) {
+                mock(AudioServerPermissionProvider.class)) {
             @Override
             public int getDeviceForStream(int stream) {
                 return AudioSystem.DEVICE_OUT_SPEAKER;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
index 4645156..2cb02bd 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
@@ -78,7 +78,7 @@
         mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
                 mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock,
                 mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
-                mock(AudioServerPermissionProvider.class), r -> r.run()) {
+                mock(AudioServerPermissionProvider.class)) {
             @Override
             public int getDeviceForStream(int stream) {
                 return AudioSystem.DEVICE_OUT_SPEAKER;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
index b7100ea..037c3c0 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
@@ -87,7 +87,7 @@
                 .thenReturn(AppOpsManager.MODE_ALLOWED);
         mAudioService = new AudioService(mContext, mSpyAudioSystem, mSpySystemServer,
                 mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy, null,
-                mMockAppOpsManager, mMockPermissionEnforcer, mMockPermissionProvider, r -> r.run());
+                mMockAppOpsManager, mMockPermissionEnforcer, mMockPermissionProvider);
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
index 746645a..27b552f 100644
--- a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
@@ -78,7 +78,7 @@
         mAudioService = new AudioService(mContext, mAudioSystem, mSystemServer,
                 mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock,
                 mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
-                mock(AudioServerPermissionProvider.class), r -> r.run());
+                mock(AudioServerPermissionProvider.class));
         mTestLooper.dispatchAll();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
index e45ab31..8e34ee1 100644
--- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
@@ -160,7 +160,7 @@
                 @NonNull PermissionEnforcer enforcer,
                 AudioServerPermissionProvider permissionProvider) {
             super(context, audioSystem, systemServer, settings, audioVolumeGroupHelper,
-                    audioPolicy, looper, appOps, enforcer, permissionProvider, r -> r.run());
+                    audioPolicy, looper, appOps, enforcer, permissionProvider);
         }
 
         public void setDeviceForStream(int stream, int device) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
index 633a3c9..901c036 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -54,6 +54,7 @@
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.AtomicFile;
 import android.util.SparseArray;
+import android.view.HapticFeedbackConstants;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -292,7 +293,7 @@
 
         for (int effectId : BIOMETRIC_FEEDBACK_CONSTANTS) {
             VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
-                    effectId, /* bypassVibrationIntensitySetting= */ false, /* fromIme= */ false);
+                    effectId, /* flags */ 0, /* privFlags */ 0);
             assertThat(attrs.getUsage()).isEqualTo(VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
         }
     }
@@ -302,8 +303,7 @@
         HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
 
         VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
-                SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ false,
-                false /* fromIme*/);
+                SAFE_MODE_ENABLED, /* flags */ 0, /* privFlags */ 0);
 
         assertThat(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)).isFalse();
     }
@@ -313,7 +313,8 @@
         HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
 
         VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
-                SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ true, false /* fromIme*/);
+                SAFE_MODE_ENABLED,
+                /* flags */ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING, /* privFlags */ 0);
 
         assertThat(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)).isTrue();
     }
@@ -325,7 +326,7 @@
 
         for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
             VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
-                    effectId, /* bypassVibrationIntensitySetting= */ false, false /* fromIme*/);
+                    effectId, /* flags */ 0, /* privFlags */ 0);
             assertWithMessage("Expected FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId)
                    .that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isTrue();
         }
@@ -338,7 +339,7 @@
 
         for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
             VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
-                    effectId, /* bypassVibrationIntensitySetting= */ false, false /* fromIme*/);
+                    effectId, /* flags */ 0, /* privFlags */ 0);
             assertWithMessage("Expected no FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId)
                    .that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isFalse();
         }
@@ -351,7 +352,8 @@
 
         for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
             VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
-                    effectId, /* bypassVibrationIntensitySetting= */ false, true /* fromIme*/);
+                    effectId, /* flags */ 0,
+                    HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
             assertWithMessage("Expected USAGE_TOUCH for effect " + effectId)
                     .that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
             assertWithMessage("Expected no CATEGORY_KEYBOARD for effect " + effectId)
@@ -366,7 +368,7 @@
 
         for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
             VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
-                    effectId, /* bypassVibrationIntensitySetting= */ false, false /* fromIme*/);
+                    effectId, /* flags */ 0, /* privFlags */ 0);
             assertWithMessage("Expected USAGE_TOUCH for effect " + effectId)
                     .that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
             assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId)
@@ -381,7 +383,8 @@
 
         for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
             VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
-                    effectId, /* bypassVibrationIntensitySetting= */ false, true /* fromIme*/);
+                    effectId, /* flags */ 0,
+                    HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
             assertWithMessage("Expected USAGE_TOUCH for effect " + effectId)
                     .that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
             assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId)
@@ -398,7 +401,8 @@
 
         for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
             VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
-                    effectId, /* bypassVibrationIntensitySetting= */ false, true /* fromIme*/);
+                    effectId, /* flags */ 0,
+                    HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
             assertWithMessage("Expected no FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect "
                     + effectId)
                     .that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isFalse();
@@ -414,7 +418,7 @@
 
         for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
             VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
-                    effectId, /* bypassVibrationIntensitySetting= */ false, false /* fromIme*/);
+                    effectId, /* flags */ 0, /* privFlags */ 0);
             assertWithMessage("Expected no FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect "
                     + effectId)
                     .that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isFalse();
@@ -430,7 +434,8 @@
 
         for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
             VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
-                    effectId, /* bypassVibrationIntensitySetting= */ false, true /* fromIme*/);
+                    effectId, /* flags */ 0,
+                    HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
             assertWithMessage("Expected FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect "
                     + effectId)
                     .that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isTrue();
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 1875284..ef944db 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -2661,9 +2661,10 @@
 
     private HalVibration performHapticFeedbackAndWaitUntilFinished(VibratorManagerService service,
                 int constant, boolean always) throws InterruptedException {
-        HalVibration vib =
-                service.performHapticFeedbackInternal(UID, Context.DEVICE_ID_DEFAULT, PACKAGE_NAME,
-                        constant, always, "some reason", service, false /* fromIme */);
+        HalVibration vib = service.performHapticFeedbackInternal(UID, Context.DEVICE_ID_DEFAULT,
+                PACKAGE_NAME, constant, "some reason", service,
+                always ? HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING : 0 /* flags */,
+                0 /* privFlags */);
         if (vib != null) {
             vib.waitForEnd();
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index a4bec64..ad3fba3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -166,9 +166,9 @@
                 ACTIVITY_TYPE_STANDARD).setDisplay(display).build();
 
         final int desiredWidth =
-                (int) (DISPLAY_STABLE_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+                (int) (DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
         final int desiredHeight =
-                (int) (DISPLAY_STABLE_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+                (int) (DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
         assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 38ad9a7..4b0668f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -270,8 +270,8 @@
     }
 
     @Override
-    public boolean performHapticFeedback(int uid, String packageName, int effectId,
-            boolean always, String reason, boolean fromIme) {
+    public boolean performHapticFeedback(int uid, String packageName, int effectId, String reason,
+            int flags, int privFlags) {
         return false;
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 95d37eb..7d01b79 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -35,6 +35,7 @@
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.window.TransitionInfo.FLAG_CONFIG_AT_END;
+import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
 import static android.window.TransitionInfo.FLAG_FILLS_TASK;
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
@@ -77,13 +78,14 @@
 
 import android.app.ActivityManager;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.IBinder;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.view.SurfaceControl;
@@ -95,6 +97,7 @@
 import android.window.ITransitionPlayer;
 import android.window.RemoteTransition;
 import android.window.SystemPerformanceHinter;
+import android.window.TaskFragmentAnimationParams;
 import android.window.TaskFragmentOrganizer;
 import android.window.TransitionInfo;
 
@@ -104,7 +107,6 @@
 import com.android.internal.graphics.ColorUtils;
 import com.android.window.flags.Flags;
 
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -120,16 +122,15 @@
  * Build/Install/Run:
  *  atest WmTests:TransitionTests
  */
-@EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
 @SmallTest
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class TransitionTests extends WindowTestsBase {
     final SurfaceControl.Transaction mMockT = mock(SurfaceControl.Transaction.class);
     private BLASTSyncEngine mSyncEngine;
+    private Transition mTransition;
+    private TransitionInfo mInfo;
 
-    @Rule
-    public SetFlagsRule mRule = new SetFlagsRule();
     private Transition createTestTransition(int transitType, TransitionController controller) {
         final Transition transition = new Transition(transitType, 0 /* flags */, controller,
                 controller.mSyncEngine);
@@ -1994,6 +1995,221 @@
         assertEquals(expectedBackgroundColor, info.getChanges().get(1).getBackgroundColor());
     }
 
+    @DisableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
+    @Test
+    public void testOverrideAnimationOptionsToInfoIfNecessary_disableAnimOptionsPerChange() {
+        initializeOverrideAnimationOptionsTest();
+        TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
+                .makeCommonAnimOptions("testPackage");
+        mTransition.setOverrideAnimation(options, null /* startCallback */,
+                null /* finishCallback */);
+
+        mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
+
+        assertEquals(options, mInfo.getAnimationOptions());
+    }
+
+    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
+    @Test
+    public void testOverrideAnimationOptionsToInfoIfNecessary_nonCustomAnimOptions() {
+        initializeOverrideAnimationOptionsTest();
+        TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
+                .makeCommonAnimOptions("testPackage");
+        mTransition.setOverrideAnimation(options, null /* startCallback */,
+                null /* finishCallback */);
+
+        mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
+
+        final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
+        final TransitionInfo.Change taskChange = mInfo.getChanges().get(1);
+        final TransitionInfo.Change embeddedTfChange = mInfo.getChanges().get(2);
+        final TransitionInfo.Change activityChange = mInfo.getChanges().get(3);
+
+        assertNull("Display change's AnimationOptions must not be overridden.",
+                displayChange.getAnimationOptions());
+        assertNull("Task change's AnimationOptions must not be overridden.",
+                taskChange.getAnimationOptions());
+        assertNull("Embedded TF change's AnimationOptions must not be overridden.",
+                embeddedTfChange.getAnimationOptions());
+        assertEquals("Activity change's AnimationOptions must be overridden.",
+                options, activityChange.getAnimationOptions());
+    }
+
+    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
+    @Test
+    public void testOverrideAnimationOptionsToInfoIfNecessary_crossProfileAnimOptions() {
+        initializeOverrideAnimationOptionsTest();
+        TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
+                .makeCrossProfileAnimOptions();
+        mTransition.setOverrideAnimation(options, null /* startCallback */,
+                null /* finishCallback */);
+
+        final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
+        final TransitionInfo.Change taskChange = mInfo.getChanges().get(1);
+        final TransitionInfo.Change embeddedTfChange = mInfo.getChanges().get(2);
+        final TransitionInfo.Change activityChange = mInfo.getChanges().get(3);
+        activityChange.setMode(TRANSIT_OPEN);
+
+        mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
+
+        assertNull("Display change's AnimationOptions must not be overridden.",
+                displayChange.getAnimationOptions());
+        assertNull("Task change's AnimationOptions must not be overridden.",
+                taskChange.getAnimationOptions());
+        assertNull("Embedded TF change's AnimationOptions must not be overridden.",
+                embeddedTfChange.getAnimationOptions());
+        assertEquals("Activity change's AnimationOptions must be overridden.",
+                options, activityChange.getAnimationOptions());
+        assertTrue(activityChange.hasFlags(FLAG_CROSS_PROFILE_OWNER_THUMBNAIL));
+    }
+
+    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
+    @Test
+    public void testOverrideAnimationOptionsToInfoIfNecessary_customAnimOptions() {
+        initializeOverrideAnimationOptionsTest();
+        TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
+                .makeCustomAnimOptions("testPackage", Resources.ID_NULL,
+                        TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+                        TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+                        Color.GREEN, false /* overrideTaskTransition */);
+        mTransition.setOverrideAnimation(options, null /* startCallback */,
+                null /* finishCallback */);
+
+        mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
+
+        final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
+        final TransitionInfo.Change taskChange = mInfo.getChanges().get(1);
+        final TransitionInfo.Change embeddedTfChange = mInfo.getChanges().get(2);
+        final TransitionInfo.Change activityChange = mInfo.getChanges().get(3);
+
+        assertNull("Display change's AnimationOptions must not be overridden.",
+                displayChange.getAnimationOptions());
+        assertNull("Task change's AnimationOptions must not be overridden.",
+                taskChange.getAnimationOptions());
+        assertEquals("Embedded TF change's AnimationOptions must be overridden.",
+                options, embeddedTfChange.getAnimationOptions());
+        assertEquals("Embedded TF change's background color must not be overridden.",
+                0, embeddedTfChange.getBackgroundColor());
+        assertEquals("Activity change's AnimationOptions must be overridden.",
+                options, activityChange.getAnimationOptions());
+        assertEquals("Activity change's background color must be overridden.",
+                options.getBackgroundColor(), activityChange.getBackgroundColor());
+    }
+
+    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
+    @Test
+    public void testOverrideAnimationOptionsToInfoIfNecessary_haveTaskFragmentAnimParams() {
+        initializeOverrideAnimationOptionsTest();
+
+        final TaskFragment embeddedTf = mTransition.mTargets.get(2).mContainer.asTaskFragment();
+        embeddedTf.setAnimationParams(new TaskFragmentAnimationParams.Builder()
+                .setAnimationBackgroundColor(Color.RED)
+                .setOpenAnimationResId(0x12345678)
+                .build());
+
+        TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
+                .makeCustomAnimOptions("testPackage", Resources.ID_NULL,
+                        TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+                        TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+                        Color.GREEN, false /* overrideTaskTransition */);
+        mTransition.setOverrideAnimation(options, null /* startCallback */,
+                null /* finishCallback */);
+
+        final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
+        final TransitionInfo.Change taskChange = mInfo.getChanges().get(1);
+        final TransitionInfo.Change embeddedTfChange = mInfo.getChanges().get(2);
+        final TransitionInfo.Change activityChange = mInfo.getChanges().get(3);
+
+        final int expectedColor = embeddedTf.getAnimationParams().getAnimationBackgroundColor();
+        embeddedTfChange.setBackgroundColor(expectedColor);
+        final TransitionInfo.AnimationOptions expectedOptions = TransitionInfo.AnimationOptions
+                .makeCustomAnimOptions("testPackage", 0x12345678,
+                        TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+                        TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+                        0, false /* overrideTaskTransition */);
+        embeddedTfChange.setAnimationOptions(expectedOptions);
+
+        mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
+
+        assertNull("Display change's AnimationOptions must not be overridden.",
+                displayChange.getAnimationOptions());
+        assertNull("Task change's AnimationOptions must not be overridden.",
+                taskChange.getAnimationOptions());
+        assertEquals("Embedded TF change's AnimationOptions must be overridden.",
+                expectedOptions, embeddedTfChange.getAnimationOptions());
+        assertEquals("Embedded TF change's background color must not be overridden.",
+                expectedColor, embeddedTfChange.getBackgroundColor());
+        assertEquals("Activity change's AnimationOptions must be overridden.",
+                options, activityChange.getAnimationOptions());
+        assertEquals("Activity change's background color must be overridden.",
+                options.getBackgroundColor(), activityChange.getBackgroundColor());
+    }
+
+    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
+    @Test
+    public void testOverrideAnimationOptionsToInfoIfNecessary_customAnimOptionsWithTaskOverride() {
+        initializeOverrideAnimationOptionsTest();
+        TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
+                .makeCustomAnimOptions("testPackage", Resources.ID_NULL,
+                        TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+                        TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+                        Color.GREEN, true /* overrideTaskTransition */);
+        mTransition.setOverrideAnimation(options, null /* startCallback */,
+                null /* finishCallback */);
+
+        mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
+
+        final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
+        final TransitionInfo.Change taskChange = mInfo.getChanges().get(1);
+        final TransitionInfo.Change embeddedTfChange = mInfo.getChanges().get(2);
+        final TransitionInfo.Change activityChange = mInfo.getChanges().get(3);
+
+        assertNull("Display change's AnimationOptions must not be overridden.",
+                displayChange.getAnimationOptions());
+        assertEquals("Task change's AnimationOptions must be overridden.",
+                options, taskChange.getAnimationOptions());
+        assertEquals("Task change's background color must be overridden.",
+                options.getBackgroundColor(), taskChange.getBackgroundColor());
+        assertEquals("Embedded TF change's AnimationOptions must be overridden.",
+                options, embeddedTfChange.getAnimationOptions());
+        assertEquals("Embedded TF change's background color must be overridden.",
+                0, embeddedTfChange.getBackgroundColor());
+        assertEquals("Activity change's AnimationOptions must be overridden.",
+                options, activityChange.getAnimationOptions());
+        assertEquals("Activity change's background color must be overridden.",
+                options.getBackgroundColor(), activityChange.getBackgroundColor());
+    }
+
+    private void initializeOverrideAnimationOptionsTest() {
+        mTransition = createTestTransition(TRANSIT_OPEN);
+
+        // Test set AnimationOptions for Activity and Task.
+        final Task task = createTask(mDisplayContent);
+        // Create an embedded TaskFragment.
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        registerTaskFragmentOrganizer(
+                ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
+        final TaskFragment embeddedTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final ActivityRecord nonEmbeddedActivity = createActivityRecord(task);
+        mWm.mCurrentUserId = nonEmbeddedActivity.mUserId;
+
+        mTransition.mTargets = new ArrayList<>();
+        mTransition.mTargets.add(new Transition.ChangeInfo(mDisplayContent));
+        mTransition.mTargets.add(new Transition.ChangeInfo(task));
+        mTransition.mTargets.add(new Transition.ChangeInfo(embeddedTf));
+        mTransition.mTargets.add(new Transition.ChangeInfo(nonEmbeddedActivity));
+
+        mInfo = new TransitionInfo(TRANSIT_OPEN, 0 /* flags */);
+        mInfo.addChange(new TransitionInfo.Change(mDisplayContent.mRemoteToken
+                .toWindowContainerToken(), mDisplayContent.getAnimationLeash()));
+        mInfo.addChange(new TransitionInfo.Change(task.mRemoteToken.toWindowContainerToken(),
+                task.getAnimationLeash()));
+        mInfo.addChange(new TransitionInfo.Change(embeddedTf.mRemoteToken.toWindowContainerToken(),
+                embeddedTf.getAnimationLeash()));
+        mInfo.addChange(new TransitionInfo.Change(null /* container */,
+                nonEmbeddedActivity.getAnimationLeash()));
+    }
+
     @Test
     public void testTransitionVisibleChange() {
         registerTestTransitionPlayer();
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index aebae4e..4b83b65 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -1078,6 +1078,11 @@
      * @hide
      */
     public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED = 5;
+    /**
+     * Datagram type indicating that the message to be sent or received is of type SMS.
+     * @hide
+     */
+    public static final int DATAGRAM_TYPE_SMS = 6;
 
     /** @hide */
     @IntDef(prefix = "DATAGRAM_TYPE_", value = {
@@ -1086,7 +1091,8 @@
             DATAGRAM_TYPE_LOCATION_SHARING,
             DATAGRAM_TYPE_KEEP_ALIVE,
             DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP,
-            DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED
+            DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED,
+            DATAGRAM_TYPE_SMS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface DatagramType {}