Merge "Add flicker test for snap resizing using keyboard shortcuts." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 97d28d1..2f843f9 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -345,7 +345,7 @@
     name: "android.nfc.flags-aconfig",
     package: "android.nfc",
     container: "system",
-    srcs: ["nfc/java/android/nfc/*.aconfig"],
+    srcs: ["nfc-non-updatable/flags/*.aconfig"],
 }
 
 cc_aconfig_library {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 8f44698..5dfb375 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1034,7 +1034,7 @@
                 for (int p = preferredUidOnly.size() - 1; p >= 0; --p) {
                     final ContextAssignment assignment = preferredUidOnly.get(p);
                     final JobStatus runningJob = assignment.context.getRunningJobLocked();
-                    if (runningJob.getUid() != nextPending.getUid()) {
+                    if (runningJob == null || runningJob.getUid() != nextPending.getUid()) {
                         continue;
                     }
                     final int jobBias = mService.evaluateJobBiasLocked(runningJob);
@@ -1916,8 +1916,9 @@
         for (int i = 0; i < mActiveServices.size(); i++) {
             final JobServiceContext jc = mActiveServices.get(i);
             final JobStatus js = jc.getRunningJobLocked();
-            if (jc.stopIfExecutingLocked(pkgName, userId, namespace, matchJobId, jobId,
-                    stopReason, internalStopReason)) {
+            if (js != null &&
+                    jc.stopIfExecutingLocked(pkgName, userId, namespace,
+                        matchJobId, jobId, stopReason, internalStopReason)) {
                 foundSome = true;
                 pw.print("Stopping job: ");
                 js.printUniqueId(pw);
diff --git a/core/api/current.txt b/core/api/current.txt
index ca4b2fa..32507df 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8110,7 +8110,7 @@
     method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, conditional=true) public boolean addCrossProfileWidgetProvider(@Nullable android.content.ComponentName, String);
     method public int addOverrideApn(@NonNull android.content.ComponentName, @NonNull android.telephony.data.ApnSetting);
     method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK_TASK, conditional=true) public void addPersistentPreferredActivity(@Nullable android.content.ComponentName, android.content.IntentFilter, @NonNull android.content.ComponentName);
-    method public void addUserRestriction(@NonNull android.content.ComponentName, String);
+    method public void addUserRestriction(@Nullable android.content.ComponentName, String);
     method public void addUserRestrictionGlobally(@NonNull String);
     method public boolean bindDeviceAdminServiceAsUser(@NonNull android.content.ComponentName, @NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle);
     method public boolean bindDeviceAdminServiceAsUser(@NonNull android.content.ComponentName, @NonNull android.content.Intent, @NonNull android.content.ServiceConnection, @NonNull android.content.Context.BindServiceFlags, @NonNull android.os.UserHandle);
@@ -8122,7 +8122,7 @@
     method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK_TASK, conditional=true) public void clearPackagePersistentPreferredActivities(@Nullable android.content.ComponentName, String);
     method @Deprecated public void clearProfileOwner(@NonNull android.content.ComponentName);
     method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_RESET_PASSWORD, conditional=true) public boolean clearResetPasswordToken(@Nullable android.content.ComponentName);
-    method public void clearUserRestriction(@NonNull android.content.ComponentName, String);
+    method public void clearUserRestriction(@Nullable android.content.ComponentName, String);
     method public android.content.Intent createAdminSupportIntent(@NonNull String);
     method @Nullable public android.os.UserHandle createAndManageUser(@NonNull android.content.ComponentName, @NonNull String, @NonNull android.content.ComponentName, @Nullable android.os.PersistableBundle, int);
     method public void enableSystemApp(@NonNull android.content.ComponentName, String);
@@ -8183,7 +8183,7 @@
     method @Deprecated @ColorInt public int getOrganizationColor(@NonNull android.content.ComponentName);
     method @Nullable @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, conditional=true) public CharSequence getOrganizationName(@Nullable android.content.ComponentName);
     method public java.util.List<android.telephony.data.ApnSetting> getOverrideApns(@NonNull android.content.ComponentName);
-    method @NonNull public android.app.admin.DevicePolicyManager getParentProfileInstance(@NonNull android.content.ComponentName);
+    method @NonNull public android.app.admin.DevicePolicyManager getParentProfileInstance(@Nullable android.content.ComponentName);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY}, conditional=true) public int getPasswordComplexity();
     method public long getPasswordExpiration(@Nullable android.content.ComponentName);
     method public long getPasswordExpirationTimeout(@Nullable android.content.ComponentName);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 9ddc729..39c27a1 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -12190,13 +12190,6 @@
      * be enforced device-wide. These constants will also state in their documentation which
      * permission is required to manage the restriction using this API.
      *
-     * <p>For callers targeting Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or
-     * above, calling this API will result in applying the restriction locally on the calling user,
-     * or locally on the parent profile if called from the
-     * {@link DevicePolicyManager} instance obtained from
-     * {@link #getParentProfileInstance(ComponentName)}. To set a restriction globally, call
-     * {@link #addUserRestrictionGlobally} instead.
-     *
      * <p>
      * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, after the user restriction
      * policy has been set, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String,
@@ -12217,13 +12210,18 @@
      * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult}
      * will contain the reason why the policy changed.
      *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with or
+     * {@code null} if the caller is not a device admin.
      * @param key   The key of the restriction.
      * @throws SecurityException if {@code admin} is not a device or profile owner and if the caller
      * has not been granted the permission to set the given user restriction.
      */
+    // NB: For permission-based callers using this API will result in applying the restriction
+    // locally on the calling user or locally on the parent profile if called from through parent
+    // instance. To set a restriction globally, call addUserRestrictionGlobally() instead.
+    // Permission-based callers must target Android U or above.
     @SupportsCoexistence
-    public void addUserRestriction(@NonNull ComponentName admin,
+    public void addUserRestriction(@Nullable ComponentName admin,
             @UserManager.UserRestrictionKey String key) {
         if (mService != null) {
             try {
@@ -12358,10 +12356,6 @@
      * constants state in their documentation which permission is required to manage the restriction
      * using this API.
      *
-     * <p>For callers targeting Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or
-     * above, calling this API will result in clearing any local and global restriction with the
-     * specified key that was previously set by the caller.
-     *
      * <p>
      * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, after the user restriction
      * policy has been cleared, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String,
@@ -12382,13 +12376,17 @@
      * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult}
      * will contain the reason why the policy changed.
      *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with or
+     * {@code null} if the caller is not a device admin.
      * @param key   The key of the restriction.
      * @throws SecurityException if {@code admin} is not a device or profile owner  and if the
      *  caller has not been granted the permission to set the given user restriction.
      */
+    // NB: For permission-based callers using this API will result in clearing any local and global
+    // restriction with the specified key that was previously set by the caller.
+    // Permission-based callers must target Android U or above.
     @SupportsCoexistence
-    public void clearUserRestriction(@NonNull ComponentName admin,
+    public void clearUserRestriction(@Nullable ComponentName admin,
             @UserManager.UserRestrictionKey String key) {
         if (mService != null) {
             try {
@@ -14556,8 +14554,8 @@
     }
 
     /**
-     * Called by the profile owner of a managed profile to obtain a {@link DevicePolicyManager}
-     * whose calls act on the parent profile.
+     * Called by the profile owner of a managed profile or other apps in a managed profile to
+     * obtain a {@link DevicePolicyManager} whose calls act on the parent profile.
      *
      * <p>The following methods are supported for the parent instance, all other methods will
      * throw a SecurityException when called on the parent instance:
@@ -14614,10 +14612,12 @@
      * <li>{@link #wipeData}</li>
      * </ul>
      *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with or
+     *         {@code null} if the caller is not a profile owner.
      * @return a new instance of {@link DevicePolicyManager} that acts on the parent profile.
-     * @throws SecurityException if {@code admin} is not a profile owner.
+     * @throws SecurityException if the current user is not a managed profile.
      */
-    public @NonNull DevicePolicyManager getParentProfileInstance(@NonNull ComponentName admin) {
+    public @NonNull DevicePolicyManager getParentProfileInstance(@Nullable ComponentName admin) {
         throwIfParentInstance("getParentProfileInstance");
         UserManager um = mContext.getSystemService(UserManager.class);
         if (!um.isManagedProfile()) {
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 47ef461..9f8505f 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -125,7 +125,10 @@
     public static final int KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK = 75;
     public static final int KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW = 76;
     public static final int KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB = 77;
-
+    public static final int KEY_GESTURE_TYPE_MAGNIFIER_PAN_LEFT = 78;
+    public static final int KEY_GESTURE_TYPE_MAGNIFIER_PAN_RIGHT = 79;
+    public static final int KEY_GESTURE_TYPE_MAGNIFIER_PAN_UP = 80;
+    public static final int KEY_GESTURE_TYPE_MAGNIFIER_PAN_DOWN = 81;
 
     public static final int FLAG_CANCELLED = 1;
 
@@ -217,7 +220,11 @@
             KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION,
             KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK,
             KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
-            KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB
+            KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB,
+            KEY_GESTURE_TYPE_MAGNIFIER_PAN_LEFT,
+            KEY_GESTURE_TYPE_MAGNIFIER_PAN_RIGHT,
+            KEY_GESTURE_TYPE_MAGNIFIER_PAN_UP,
+            KEY_GESTURE_TYPE_MAGNIFIER_PAN_DOWN,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface KeyGestureType {
@@ -792,6 +799,14 @@
                 return "KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW";
             case KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB:
                 return "KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB";
+            case KEY_GESTURE_TYPE_MAGNIFIER_PAN_LEFT:
+                return "KEY_GESTURE_TYPE_MAGNIFIER_PAN_LEFT";
+            case KEY_GESTURE_TYPE_MAGNIFIER_PAN_RIGHT:
+                return "KEY_GESTURE_TYPE_MAGNIFIER_PAN_RIGHT";
+            case KEY_GESTURE_TYPE_MAGNIFIER_PAN_UP:
+                return "KEY_GESTURE_TYPE_MAGNIFIER_PAN_UP";
+            case KEY_GESTURE_TYPE_MAGNIFIER_PAN_DOWN:
+                return "KEY_GESTURE_TYPE_MAGNIFIER_PAN_DOWN";
             default:
                 return Integer.toHexString(value);
         }
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 289c5cf..be69d3d 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -86,7 +86,9 @@
     ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX(
             Flags::enableDesktopAppLaunchAlttabTransitionsBugfix, false),
     ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX(
-            Flags::enableDesktopAppLaunchTransitionsBugfix, false);
+            Flags::enableDesktopAppLaunchTransitionsBugfix, false),
+    INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC(
+            Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true);
 
     private static final String TAG = "DesktopModeFlagsUtil";
     // Function called to obtain aconfig flag value.
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 1707e61b..3b77b1f 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -16,6 +16,17 @@
 }
 
 flag {
+    name: "include_top_transparent_fullscreen_task_in_desktop_heuristic"
+    namespace: "lse_desktop_experience"
+    description: "Whether to include any top transparent fullscreen task launched in desktop /n"
+                 "mode in the heuristic for if desktop windowing is showing or not."
+    bug: "379543275"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "enable_windowing_dynamic_initial_bounds"
     namespace: "lse_desktop_experience"
     description: "Enables new initial bounds for desktop windowing which adjust depending on app constraints"
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index d0d4af6..5a092b8 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -127,3 +127,14 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    namespace: "windowing_sdk"
+    name: "track_system_ui_context_before_wms"
+    description: "Keep track of SystemUiContext before WMS is initialized"
+    bug: "384428048"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4a948dd..e133ca4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -9338,7 +9338,11 @@
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
-        <service android:name="com.android.server.profcollect.ProfcollectForwardingService$ProfcollectBGJobService"
+        <service android:name="com.android.server.profcollect.ProfcollectForwardingService$PeriodicTraceJobService"
+                 android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+
+        <service android:name="com.android.server.profcollect.ProfcollectForwardingService$ReportProcessJobService"
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a9c0667..565e28e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7277,4 +7277,7 @@
          features. Examples include the search functionality or the app
          predictor. -->
     <string name="config_systemVendorIntelligence" translatable="false"></string>
+
+    <!-- Whether the device supports Wi-Fi USD feature. -->
+    <bool name="config_deviceSupportsWifiUsd">false</bool>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 3afb9d2..73e06f6 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5801,5 +5801,7 @@
   <java-symbol type="dimen" name="config_shapeCornerRadiusLarge"/>
   <java-symbol type="dimen" name="config_shapeCornerRadiusXlarge"/>
 
+  <!-- Whether the device supports Wi-Fi USD feature. -->
+  <java-symbol type="bool" name="config_deviceSupportsWifiUsd" />
 
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
index bc56637..d1dcc9b1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
@@ -18,22 +18,29 @@
 
 package com.android.wm.shell.compatui
 
-import android.app.TaskInfo
+import android.app.ActivityManager.RunningTaskInfo
 import android.content.Context
 import com.android.internal.R
 
 // TODO(b/347289970): Consider replacing with API
 /**
  * If the top activity should be exempt from desktop windowing and forced back to fullscreen.
- * Currently includes all system ui activities and modal dialogs. However is the top activity is not
+ * Currently includes all system ui activities and modal dialogs. However if the top activity is not
  * being displayed, regardless of its configuration, we will not exempt it as to remain in the
  * desktop windowing environment.
  */
-fun isTopActivityExemptFromDesktopWindowing(context: Context, task: TaskInfo) =
-    (isSystemUiTask(context, task) || (task.numActivities > 0 && task.isActivityStackTransparent))
+fun isTopActivityExemptFromDesktopWindowing(context: Context, task: RunningTaskInfo) =
+    (isSystemUiTask(context, task) || isTransparentTask(task))
             && !task.isTopActivityNoDisplay
 
-private fun isSystemUiTask(context: Context, task: TaskInfo): Boolean {
+/**
+ * Returns true if all activities in a tasks stack are transparent. If there are no activities will
+ * return false.
+ */
+fun isTransparentTask(task: RunningTaskInfo): Boolean = task.isActivityStackTransparent
+        && task.numActivities > 0
+
+private fun isSystemUiTask(context: Context, task: RunningTaskInfo): Boolean {
     val sysUiPackageName: String =
         context.resources.getString(R.string.config_systemUi)
     return task.baseActivity?.packageName == sysUiPackageName
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index c4abee3..c5b570dd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -54,7 +54,9 @@
      * @property closingTasks task ids for tasks that are going to close, but are currently visible.
      * @property freeformTasksInZOrder list of current freeform task ids ordered from top to bottom
      * @property fullImmersiveTaskId the task id of the desktop task that is in full-immersive mode.
-     *   (top is at index 0).
+     * @property topTransparentFullscreenTaskId the task id of any current top transparent
+     *   fullscreen task launched on top of Desktop Mode. Cleared when the transparent task is
+     *   closed or sent to back. (top is at index 0).
      */
     private data class DesktopTaskData(
         val activeTasks: ArraySet<Int> = ArraySet(),
@@ -64,6 +66,7 @@
         val closingTasks: ArraySet<Int> = ArraySet(),
         val freeformTasksInZOrder: ArrayList<Int> = ArrayList(),
         var fullImmersiveTaskId: Int? = null,
+        var topTransparentFullscreenTaskId: Int? = null,
     ) {
         fun deepCopy(): DesktopTaskData =
             DesktopTaskData(
@@ -73,6 +76,7 @@
                 closingTasks = ArraySet(closingTasks),
                 freeformTasksInZOrder = ArrayList(freeformTasksInZOrder),
                 fullImmersiveTaskId = fullImmersiveTaskId,
+                topTransparentFullscreenTaskId = topTransparentFullscreenTaskId,
             )
 
         fun clear() {
@@ -82,6 +86,7 @@
             closingTasks.clear()
             freeformTasksInZOrder.clear()
             fullImmersiveTaskId = null
+            topTransparentFullscreenTaskId = null
         }
     }
 
@@ -322,13 +327,27 @@
     fun getTaskInFullImmersiveState(displayId: Int): Int? =
         desktopTaskDataByDisplayId.getOrCreate(displayId).fullImmersiveTaskId
 
+    /** Sets the top transparent fullscreen task id for a given display. */
+    fun setTopTransparentFullscreenTaskId(displayId: Int, taskId: Int) {
+        desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId = taskId
+    }
+
+    /** Returns the top transparent fullscreen task id for a given display, or null. */
+    fun getTopTransparentFullscreenTaskId(displayId: Int): Int? =
+        desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId
+
+    /** Clears the top transparent fullscreen task id info for a given display. */
+    fun clearTopTransparentFullscreenTaskId(displayId: Int) {
+        desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId = null
+    }
+
     private fun notifyVisibleTaskListeners(displayId: Int, visibleTasksCount: Int) {
         visibleTasksListeners.forEach { (listener, executor) ->
             executor.execute { listener.onTasksVisibilityChanged(displayId, visibleTasksCount) }
         }
     }
 
-    /** Gets number of visible tasks on given [displayId] */
+    /** Gets number of visible freeform tasks on given [displayId] */
     fun getVisibleTaskCount(displayId: Int): Int =
         desktopTaskDataByDisplayId[displayId]?.visibleTasks?.size
             ?: 0.also { logD("getVisibleTaskCount=$it") }
@@ -526,6 +545,10 @@
             )
             pw.println("${innerPrefix}minimizedTasks=${data.minimizedTasks.toDumpString()}")
             pw.println("${innerPrefix}fullImmersiveTaskId=${data.fullImmersiveTaskId}")
+            pw.println(
+                "${innerPrefix}topTransparentFullscreenTaskId=" +
+                    "${data.topTransparentFullscreenTaskId}"
+            )
             pw.println("${innerPrefix}wallpaperActivityToken=$wallpaperActivityToken")
         }
     }
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 d966aaa..a3d3a90 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
@@ -21,7 +21,6 @@
 import android.app.ActivityOptions
 import android.app.KeyguardManager
 import android.app.PendingIntent
-import android.app.TaskInfo
 import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
 import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -84,6 +83,7 @@
 import com.android.wm.shell.common.SingleInstanceRemoteListener
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
+import com.android.wm.shell.compatui.isTransparentTask
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
 import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum
@@ -308,11 +308,23 @@
         }
     }
 
-    /** Gets number of visible tasks in [displayId]. */
+    /** Gets number of visible freeform tasks in [displayId]. */
     fun visibleTaskCount(displayId: Int): Int = taskRepository.getVisibleTaskCount(displayId)
 
-    /** Returns true if any tasks are visible in Desktop Mode. */
-    fun isDesktopModeShowing(displayId: Int): Boolean = visibleTaskCount(displayId) > 0
+    /**
+     * Returns true if any freeform tasks are visible or if a transparent fullscreen task exists on
+     * top in Desktop Mode.
+     */
+    fun isDesktopModeShowing(displayId: Int): Boolean {
+        if (
+            DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC
+                .isTrue() && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
+        ) {
+            return visibleTaskCount(displayId) > 0 ||
+                taskRepository.getTopTransparentFullscreenTaskId(displayId) != null
+        }
+        return visibleTaskCount(displayId) > 0
+    }
 
     /** Moves focused task to desktop mode for given [displayId]. */
     fun moveFocusedTaskToDesktop(displayId: Int, transitionSource: DesktopModeTransitionSource) {
@@ -1596,7 +1608,7 @@
             TransitionUtil.isOpeningType(request.type) &&
             taskRepository.isActiveTask(triggerTask.taskId))
 
-    private fun isIncompatibleTask(task: TaskInfo) =
+    private fun isIncompatibleTask(task: RunningTaskInfo) =
         DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() &&
             isTopActivityExemptFromDesktopWindowing(context, task)
 
@@ -1852,6 +1864,15 @@
      * fullscreen.
      */
     private fun handleIncompatibleTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
+        logV("handleIncompatibleTaskLaunch")
+        if (!isDesktopModeShowing(task.displayId)) return null
+        // Only update task repository for transparent task.
+        if (
+            DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC
+                .isTrue() && isTransparentTask(task)
+        ) {
+            taskRepository.setTopTransparentFullscreenTaskId(task.displayId, task.taskId)
+        }
         // Already fullscreen, no-op.
         if (task.isFullscreen) return null
         return WindowContainerTransaction().also { wct -> addMoveToFullscreenChanges(wct, task) }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index 9625b71..5c79658 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -75,6 +75,12 @@
         finishTransaction: SurfaceControl.Transaction,
     ) {
         // TODO: b/332682201 Update repository state
+        if (
+            DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC
+                .isTrue() && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
+        ) {
+            updateTopTransparentFullscreenTaskId(info)
+        }
         updateWallpaperToken(info)
         if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
             handleBackNavigation(transition, info)
@@ -264,4 +270,22 @@
             }
         }
     }
+
+    private fun updateTopTransparentFullscreenTaskId(info: TransitionInfo) {
+        info.changes.forEach { change ->
+            change.taskInfo?.let { task ->
+                val desktopRepository = desktopUserRepositories.getProfile(task.userId)
+                val displayId = task.displayId
+                // Clear `topTransparentFullscreenTask` information from repository if task
+                // is closed or sent to back.
+                if (
+                    TransitionUtil.isClosingMode(change.mode) &&
+                        task.taskId ==
+                            desktopRepository.getTopTransparentFullscreenTaskId(displayId)
+                ) {
+                    desktopRepository.clearTopTransparentFullscreenTaskId(displayId)
+                }
+            }
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
index 3ab7b34..7157a7f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
@@ -27,10 +27,9 @@
 import org.junit.runner.RunWith
 
 /**
- * Tests for {@link AppCompatUtils}.
+ * Tests for [@link AppCompatUtils].
  *
- * Build/Install/Run:
- *  atest WMShellUnitTests:AppCompatUtilsTest
+ * Build/Install/Run: atest WMShellUnitTests:AppCompatUtilsTest
  */
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index e777ec7..5629127 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -56,6 +56,11 @@
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
+/**
+ * Tests for [@link DesktopRepository].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:DesktopRepositoryTest
+ */
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @ExperimentalCoroutinesApi
@@ -978,6 +983,22 @@
     }
 
     @Test
+    fun setTaskIdAsTopTransparentFullscreenTaskId_savesTaskId() {
+        repo.setTopTransparentFullscreenTaskId(displayId = DEFAULT_DISPLAY, taskId = 1)
+
+        assertThat(repo.getTopTransparentFullscreenTaskId(DEFAULT_DISPLAY)).isEqualTo(1)
+    }
+
+    @Test
+    fun clearTaskIdAsTopTransparentFullscreenTaskId_clearsTaskId() {
+        repo.setTopTransparentFullscreenTaskId(displayId = DEFAULT_DISPLAY, taskId = 1)
+
+        repo.clearTopTransparentFullscreenTaskId(DEFAULT_DISPLAY)
+
+        assertThat(repo.getTopTransparentFullscreenTaskId(DEFAULT_DISPLAY)).isNull()
+    }
+
+    @Test
     fun setTaskInFullImmersiveState_savedAsInImmersiveState() {
         assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isFalse()
 
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 0283c02..4f37180b 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
@@ -545,6 +545,18 @@
     }
 
     @Test
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+        Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+    )
+    fun isDesktopModeShowing_topTransparentFullscreenTask_returnsTrue() {
+        val topTransparentTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+        taskRepository.setTopTransparentFullscreenTaskId(DEFAULT_DISPLAY, topTransparentTask.taskId)
+
+        assertThat(controller.isDesktopModeShowing(displayId = DEFAULT_DISPLAY)).isTrue()
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() {
         val homeTask = setUpHomeTask(SECOND_DISPLAY)
@@ -2623,6 +2635,63 @@
     }
 
     @Test
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+        Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+    )
+    fun handleRequest_topActivityTransparentWithDisplay_savedToDesktopRepository() {
+        val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+        markTaskVisible(freeformTask)
+
+        val transparentTask =
+            setUpFreeformTask(displayId = DEFAULT_DISPLAY).apply {
+                isActivityStackTransparent = true
+                isTopActivityNoDisplay = false
+                numActivities = 1
+            }
+
+        controller.handleRequest(Binder(), createTransition(transparentTask))
+        assertThat(taskRepository.getTopTransparentFullscreenTaskId(DEFAULT_DISPLAY))
+            .isEqualTo(transparentTask.taskId)
+    }
+
+    @Test
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+        Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+    )
+    fun handleRequest_desktopNotShowing_topTransparentFullscreenTask_notSavedToDesktopRepository() {
+        val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+        controller.handleRequest(Binder(), createTransition(task))
+        assertThat(taskRepository.getTopTransparentFullscreenTaskId(DEFAULT_DISPLAY)).isNull()
+    }
+
+    @Test
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+        Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+    )
+    fun handleRequest_onlyTopTransparentFullscreenTask_returnSwitchToFreeformWCT() {
+        val topTransparentTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+        taskRepository.setTopTransparentFullscreenTaskId(DEFAULT_DISPLAY, topTransparentTask.taskId)
+
+        val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+        val result = controller.handleRequest(Binder(), createTransition(task))
+        assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+            .isEqualTo(WINDOWING_MODE_FREEFORM)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+    fun handleRequest_desktopNotShowing_topTransparentFullscreenTask_returnNull() {
+        val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+        assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
     fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT() {
         val freeformTask = setUpFreeformTask()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index c9623bc..c66d203 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -24,6 +24,7 @@
 import android.content.Intent
 import android.os.IBinder
 import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.WindowManager
 import android.view.WindowManager.TRANSIT_CLOSE
@@ -63,8 +64,15 @@
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
+/**
+ * Tests for [@link DesktopTasksTransitionObserver].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:DesktopTasksTransitionObserverTest
+ */
 class DesktopTasksTransitionObserverTest {
 
+    @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
     @JvmField
     @Rule
     val extendedMockitoRule =
@@ -245,6 +253,48 @@
         wct.assertRemoveAt(index = 0, wallpaperToken)
     }
 
+    @Test
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+        Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+    )
+    fun topTransparentTaskClosed_clearTaskIdFromRepository() {
+        val mockTransition = Mockito.mock(IBinder::class.java)
+        val topTransparentTask = createTaskInfo(1)
+        whenever(taskRepository.getTopTransparentFullscreenTaskId(any()))
+            .thenReturn(topTransparentTask.taskId)
+
+        transitionObserver.onTransitionReady(
+            transition = mockTransition,
+            info = createCloseTransition(topTransparentTask),
+            startTransaction = mock(),
+            finishTransaction = mock(),
+        )
+
+        verify(taskRepository).clearTopTransparentFullscreenTaskId(topTransparentTask.displayId)
+    }
+
+    @Test
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+        Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+    )
+    fun topTransparentTaskSentToBack_clearTaskIdFromRepository() {
+        val mockTransition = Mockito.mock(IBinder::class.java)
+        val topTransparentTask = createTaskInfo(1)
+        whenever(taskRepository.getTopTransparentFullscreenTaskId(any()))
+            .thenReturn(topTransparentTask.taskId)
+
+        transitionObserver.onTransitionReady(
+            transition = mockTransition,
+            info = createToBackTransition(topTransparentTask),
+            startTransaction = mock(),
+            finishTransaction = mock(),
+        )
+
+        verify(taskRepository).clearTopTransparentFullscreenTaskId(topTransparentTask.displayId)
+    }
+
     private fun createBackNavigationTransition(
         task: RunningTaskInfo?,
         type: Int = TRANSIT_TO_BACK,
@@ -301,6 +351,19 @@
         }
     }
 
+    private fun createToBackTransition(task: RunningTaskInfo?): TransitionInfo {
+        return TransitionInfo(TRANSIT_TO_BACK, 0 /* flags */).apply {
+            addChange(
+                Change(mock(), mock()).apply {
+                    mode = TRANSIT_TO_BACK
+                    parent = null
+                    taskInfo = task
+                    flags = flags
+                }
+            )
+        }
+    }
+
     private fun getLatestWct(
         @WindowManager.TransitionType type: Int = TRANSIT_OPEN,
         handlerClass: Class<out Transitions.TransitionHandler>? = null,
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 4f7132a..f7f10df 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -324,19 +324,6 @@
     }
 
     /**
-     * Stops projection.
-     * @hide
-     */
-    public void stop(@StopReason int stopReason) {
-        try {
-            Log.d(TAG, "Content Recording: stopping projection");
-            mImpl.stop(stopReason);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Unable to stop projection", e);
-        }
-    }
-
-    /**
      * Get the underlying IMediaProjection.
      * @hide
      */
diff --git a/nfc-non-updatable/Android.bp b/nfc-non-updatable/Android.bp
new file mode 100644
index 0000000..ff987bb
--- /dev/null
+++ b/nfc-non-updatable/Android.bp
@@ -0,0 +1,23 @@
+package {
+    default_team: "trendy_team_fwk_nfc",
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+    name: "framework-nfc-non-updatable-sources",
+    path: "java",
+    srcs: [
+        "java/android/nfc/NfcServiceManager.java",
+        "java/android/nfc/cardemulation/ApduServiceInfo.aidl",
+        "java/android/nfc/cardemulation/ApduServiceInfo.java",
+        "java/android/nfc/cardemulation/NfcFServiceInfo.aidl",
+        "java/android/nfc/cardemulation/NfcFServiceInfo.java",
+        "java/android/nfc/cardemulation/AidGroup.aidl",
+        "java/android/nfc/cardemulation/AidGroup.java",
+    ],
+}
diff --git a/nfc-non-updatable/OWNERS b/nfc-non-updatable/OWNERS
new file mode 100644
index 0000000..f46dccd
--- /dev/null
+++ b/nfc-non-updatable/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 48448
+include platform/packages/apps/Nfc:/OWNERS
\ No newline at end of file
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc-non-updatable/flags/flags.aconfig
similarity index 100%
rename from nfc/java/android/nfc/flags.aconfig
rename to nfc-non-updatable/flags/flags.aconfig
diff --git a/nfc/java/android/nfc/NfcServiceManager.java b/nfc-non-updatable/java/android/nfc/NfcServiceManager.java
similarity index 100%
rename from nfc/java/android/nfc/NfcServiceManager.java
rename to nfc-non-updatable/java/android/nfc/NfcServiceManager.java
diff --git a/nfc/java/android/nfc/cardemulation/AidGroup.aidl b/nfc-non-updatable/java/android/nfc/cardemulation/AidGroup.aidl
similarity index 100%
rename from nfc/java/android/nfc/cardemulation/AidGroup.aidl
rename to nfc-non-updatable/java/android/nfc/cardemulation/AidGroup.aidl
diff --git a/nfc/java/android/nfc/cardemulation/AidGroup.java b/nfc-non-updatable/java/android/nfc/cardemulation/AidGroup.java
similarity index 100%
rename from nfc/java/android/nfc/cardemulation/AidGroup.java
rename to nfc-non-updatable/java/android/nfc/cardemulation/AidGroup.java
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.aidl b/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.aidl
similarity index 100%
rename from nfc/java/android/nfc/cardemulation/ApduServiceInfo.aidl
rename to nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.aidl
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java
similarity index 100%
rename from nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
rename to nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java
diff --git a/nfc/java/android/nfc/cardemulation/NfcFServiceInfo.aidl b/nfc-non-updatable/java/android/nfc/cardemulation/NfcFServiceInfo.aidl
similarity index 100%
rename from nfc/java/android/nfc/cardemulation/NfcFServiceInfo.aidl
rename to nfc-non-updatable/java/android/nfc/cardemulation/NfcFServiceInfo.aidl
diff --git a/nfc/java/android/nfc/cardemulation/NfcFServiceInfo.java b/nfc-non-updatable/java/android/nfc/cardemulation/NfcFServiceInfo.java
similarity index 100%
rename from nfc/java/android/nfc/cardemulation/NfcFServiceInfo.java
rename to nfc-non-updatable/java/android/nfc/cardemulation/NfcFServiceInfo.java
diff --git a/nfc/Android.bp b/nfc/Android.bp
index 9490487..0fdb3bd 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -9,20 +9,6 @@
 }
 
 filegroup {
-    name: "framework-nfc-non-updatable-sources",
-    path: "java",
-    srcs: [
-        "java/android/nfc/NfcServiceManager.java",
-        "java/android/nfc/cardemulation/ApduServiceInfo.aidl",
-        "java/android/nfc/cardemulation/ApduServiceInfo.java",
-        "java/android/nfc/cardemulation/NfcFServiceInfo.aidl",
-        "java/android/nfc/cardemulation/NfcFServiceInfo.java",
-        "java/android/nfc/cardemulation/AidGroup.aidl",
-        "java/android/nfc/cardemulation/AidGroup.java",
-    ],
-}
-
-filegroup {
     name: "framework-nfc-updatable-sources",
     path: "java",
     srcs: [
@@ -34,9 +20,6 @@
         "//packages/apps/Nfc:__subpackages__",
         "//packages/modules/Nfc:__subpackages__",
     ],
-    exclude_srcs: [
-        ":framework-nfc-non-updatable-sources",
-    ],
 }
 
 java_sdk_library {
diff --git a/nfc/tests/Android.bp b/nfc/tests/Android.bp
index b6090e8..17fb810 100644
--- a/nfc/tests/Android.bp
+++ b/nfc/tests/Android.bp
@@ -29,7 +29,6 @@
         "androidx.test.rules",
         "androidx.test.runner",
         "androidx.test.ext.junit",
-        "framework-nfc.impl",
         "mockito-target-extended-minus-junit4",
         "frameworks-base-testutils",
         "truth",
@@ -40,16 +39,25 @@
         "testables",
     ],
     libs: [
+        "androidx.annotation_annotation",
+        "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage
+        "framework-permission-s.stubs.module_lib",
+        "framework-permission.stubs.module_lib",
         "android.test.base.stubs.system",
         "android.test.mock.stubs.system",
         "android.test.runner.stubs.system",
+        "framework-nfc.impl",
     ],
     jni_libs: [
         // Required for ExtendedMockito
         "libdexmakerjvmtiagent",
         "libstaticjvmtiagent",
     ],
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        ":framework-nfc-updatable-sources",
+        ":framework-nfc-non-updatable-sources",
+    ],
     platform_apis: true,
     certificate: "platform",
     test_suites: [
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
index 8b8ab58..31e1eb3 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
@@ -767,6 +767,70 @@
     }
 
     /**
+     * Indicates that the result of a mitigation executed during
+     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or
+     * {@link PackageHealthObserver#onExecuteBootLoopMitigation} is unknown.
+     */
+    public static final int MITIGATION_RESULT_UNKNOWN =
+            ObserverMitigationResult.MITIGATION_RESULT_UNKNOWN;
+
+    /**
+     * Indicates that a mitigation was successfully triggered or executed during
+     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or
+     * {@link PackageHealthObserver#onExecuteBootLoopMitigation}.
+     */
+    public static final int MITIGATION_RESULT_SUCCESS =
+            ObserverMitigationResult.MITIGATION_RESULT_SUCCESS;
+
+    /**
+     * Indicates that a mitigation executed during
+     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or
+     * {@link PackageHealthObserver#onExecuteBootLoopMitigation} was skipped.
+     */
+    public static final int MITIGATION_RESULT_SKIPPED =
+            ObserverMitigationResult.MITIGATION_RESULT_SKIPPED;
+
+    /**
+     * Indicates that a mitigation executed during
+     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or
+     * {@link PackageHealthObserver#onExecuteBootLoopMitigation} failed,
+     * but the failure is potentially retryable.
+     */
+    public static final int MITIGATION_RESULT_FAILURE_RETRYABLE =
+            ObserverMitigationResult.MITIGATION_RESULT_FAILURE_RETRYABLE;
+
+    /**
+     * Indicates that a mitigation executed during
+     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or
+     * {@link PackageHealthObserver#onExecuteBootLoopMitigation} failed,
+     * and the failure is not retryable.
+     */
+    public static final int MITIGATION_RESULT_FAILURE_NON_RETRYABLE =
+            ObserverMitigationResult.MITIGATION_RESULT_FAILURE_NON_RETRYABLE;
+
+    /**
+     * Possible return values of the for mitigations executed during
+     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} and
+     * {@link PackageHealthObserver#onExecuteBootLoopMitigation}.
+     * @hide
+     */
+    @Retention(SOURCE)
+    @IntDef(prefix = "MITIGATION_RESULT_", value = {
+            ObserverMitigationResult.MITIGATION_RESULT_UNKNOWN,
+            ObserverMitigationResult.MITIGATION_RESULT_SUCCESS,
+            ObserverMitigationResult.MITIGATION_RESULT_SKIPPED,
+            ObserverMitigationResult.MITIGATION_RESULT_FAILURE_RETRYABLE,
+            ObserverMitigationResult.MITIGATION_RESULT_FAILURE_NON_RETRYABLE,
+            })
+    public @interface ObserverMitigationResult {
+        int MITIGATION_RESULT_UNKNOWN = 0;
+        int MITIGATION_RESULT_SUCCESS = 1;
+        int MITIGATION_RESULT_SKIPPED = 2;
+        int MITIGATION_RESULT_FAILURE_RETRYABLE = 3;
+        int MITIGATION_RESULT_FAILURE_NON_RETRYABLE = 4;
+    }
+
+    /**
      * The minimum value that can be returned by any observer.
      * It represents that no mitigations were available.
      */
@@ -852,13 +916,20 @@
          * health check.
          *
          * @param versionedPackage the package that is failing. This may be null if a native
-         *                          service is crashing.
-         * @param failureReason   the type of failure that is occurring.
+         *                         service is crashing.
+         * @param failureReason    the type of failure that is occurring.
          * @param mitigationCount the number of times mitigation has been called for this package
-         *                        (including this time).
-         * @return {@code true} if action was executed successfully, {@code false} otherwise
+         *                         (including this time).
+         * @return {@link #MITIGATION_RESULT_SUCCESS} if the mitigation was successful,
+         *         {@link #MITIGATION_RESULT_FAILURE_RETRYABLE} if the mitigation failed but can be
+         *         retried,
+         *         {@link #MITIGATION_RESULT_FAILURE_NON_RETRYABLE} if the mitigation failed and
+         *         cannot be retried,
+         *         {@link #MITIGATION_RESULT_UNKNOWN} if the result of the mitigation is unknown,
+         *         or {@link #MITIGATION_RESULT_SKIPPED} if the mitigation was skipped.
          */
-        boolean onExecuteHealthCheckMitigation(@Nullable VersionedPackage versionedPackage,
+        @ObserverMitigationResult int onExecuteHealthCheckMitigation(
+                @Nullable VersionedPackage versionedPackage,
                 @FailureReasons int failureReason, int mitigationCount);
 
 
@@ -885,10 +956,16 @@
          * @param mitigationCount the number of times mitigation has been attempted for this
          *                        boot loop (including this time).
          *
-         * @return {@code true} if action was executed successfully, {@code false} otherwise
+         * @return {@link #MITIGATION_RESULT_SUCCESS} if the mitigation was successful,
+         *         {@link #MITIGATION_RESULT_FAILURE_RETRYABLE} if the mitigation failed but can be
+         *         retried,
+         *         {@link #MITIGATION_RESULT_FAILURE_NON_RETRYABLE} if the mitigation failed and
+         *         cannot be retried,
+         *         {@link #MITIGATION_RESULT_UNKNOWN} if the result of the mitigation is unknown,
+         *         or {@link #MITIGATION_RESULT_SKIPPED} if the mitigation was skipped.
          */
-        default boolean onExecuteBootLoopMitigation(int mitigationCount) {
-            return false;
+        default @ObserverMitigationResult int onExecuteBootLoopMitigation(int mitigationCount) {
+            return ObserverMitigationResult.MITIGATION_RESULT_SKIPPED;
         }
 
         // TODO(b/120598832): Ensure uniqueness?
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
index bad6ab7..bb9e962 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
@@ -16,6 +16,8 @@
 
 package com.android.server;
 
+import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED;
+import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS;
 import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent;
 
 import android.annotation.IntDef;
@@ -728,10 +730,10 @@
         }
 
         @Override
-        public boolean onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage,
+        public int onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage,
                 @FailureReasons int failureReason, int mitigationCount) {
             if (isDisabled()) {
-                return false;
+                return MITIGATION_RESULT_SKIPPED;
             }
             Slog.i(TAG, "Executing remediation."
                     + " failedPackage: "
@@ -753,9 +755,9 @@
                 }
                 executeRescueLevel(mContext,
                         failedPackage == null ? null : failedPackage.getPackageName(), level);
-                return true;
+                return MITIGATION_RESULT_SUCCESS;
             } else {
-                return false;
+                return MITIGATION_RESULT_SKIPPED;
             }
         }
 
@@ -796,9 +798,9 @@
         }
 
         @Override
-        public boolean onExecuteBootLoopMitigation(int mitigationCount) {
+        public int onExecuteBootLoopMitigation(int mitigationCount) {
             if (isDisabled()) {
-                return false;
+                return MITIGATION_RESULT_SKIPPED;
             }
             boolean mayPerformReboot = !shouldThrottleReboot();
             final int level;
@@ -813,7 +815,7 @@
                 level = getRescueLevel(mitigationCount, mayPerformReboot);
             }
             executeRescueLevel(mContext, /*failedPackage=*/ null, level);
-            return true;
+            return MITIGATION_RESULT_SUCCESS;
         }
 
         @Override
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index c80a1a4..c75f3aa 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -16,6 +16,8 @@
 
 package com.android.server.rollback;
 
+import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED;
+import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS;
 import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent;
 
 import android.annotation.AnyThread;
@@ -172,7 +174,7 @@
     }
 
     @Override
-    public boolean onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage,
+    public int onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage,
             @FailureReasons int rollbackReason, int mitigationCount) {
         Slog.i(TAG, "Executing remediation."
                 + " failedPackage: "
@@ -183,7 +185,7 @@
             List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
             if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
                 mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
-                return true;
+                return MITIGATION_RESULT_SUCCESS;
             }
 
             List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
@@ -198,7 +200,7 @@
         } else {
             if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
                 mHandler.post(() -> rollbackAll(rollbackReason));
-                return true;
+                return MITIGATION_RESULT_SUCCESS;
             }
 
             RollbackInfo rollback = getAvailableRollback(failedPackage);
@@ -210,7 +212,7 @@
         }
 
         // Assume rollbacks executed successfully
-        return true;
+        return MITIGATION_RESULT_SUCCESS;
     }
 
     @Override
@@ -226,15 +228,15 @@
     }
 
     @Override
-    public boolean onExecuteBootLoopMitigation(int mitigationCount) {
+    public int onExecuteBootLoopMitigation(int mitigationCount) {
         if (Flags.recoverabilityDetection()) {
             List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
 
             triggerLeastImpactLevelRollback(availableRollbacks,
                     PackageWatchdog.FAILURE_REASON_BOOT_LOOP);
-            return true;
+            return MITIGATION_RESULT_SUCCESS;
         }
-        return false;
+        return MITIGATION_RESULT_SKIPPED;
     }
 
     @Override
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
index 4fea937..ffae517 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
@@ -752,6 +752,70 @@
         return mPackagesExemptFromImpactLevelThreshold;
     }
 
+    /**
+     * Indicates that the result of a mitigation executed during
+     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or
+     * {@link PackageHealthObserver#onExecuteBootLoopMitigation} is unknown.
+     */
+    public static final int MITIGATION_RESULT_UNKNOWN =
+            ObserverMitigationResult.MITIGATION_RESULT_UNKNOWN;
+
+    /**
+     * Indicates that a mitigation was successfully triggered or executed during
+     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or
+     * {@link PackageHealthObserver#onExecuteBootLoopMitigation}.
+     */
+    public static final int MITIGATION_RESULT_SUCCESS =
+            ObserverMitigationResult.MITIGATION_RESULT_SUCCESS;
+
+    /**
+     * Indicates that a mitigation executed during
+     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or
+     * {@link PackageHealthObserver#onExecuteBootLoopMitigation} was skipped.
+     */
+    public static final int MITIGATION_RESULT_SKIPPED =
+            ObserverMitigationResult.MITIGATION_RESULT_SKIPPED;
+
+    /**
+     * Indicates that a mitigation executed during
+     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or
+     * {@link PackageHealthObserver#onExecuteBootLoopMitigation} failed,
+     * but the failure is potentially retryable.
+     */
+    public static final int MITIGATION_RESULT_FAILURE_RETRYABLE =
+            ObserverMitigationResult.MITIGATION_RESULT_FAILURE_RETRYABLE;
+
+    /**
+     * Indicates that a mitigation executed during
+     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or
+     * {@link PackageHealthObserver#onExecuteBootLoopMitigation} failed,
+     * and the failure is not retryable.
+     */
+    public static final int MITIGATION_RESULT_FAILURE_NON_RETRYABLE =
+            ObserverMitigationResult.MITIGATION_RESULT_FAILURE_NON_RETRYABLE;
+
+    /**
+     * Possible return values of the for mitigations executed during
+     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} and
+     * {@link PackageHealthObserver#onExecuteBootLoopMitigation}.
+     * @hide
+     */
+    @Retention(SOURCE)
+    @IntDef(prefix = "MITIGATION_RESULT_", value = {
+            ObserverMitigationResult.MITIGATION_RESULT_UNKNOWN,
+            ObserverMitigationResult.MITIGATION_RESULT_SUCCESS,
+            ObserverMitigationResult.MITIGATION_RESULT_SKIPPED,
+            ObserverMitigationResult.MITIGATION_RESULT_FAILURE_RETRYABLE,
+            ObserverMitigationResult.MITIGATION_RESULT_FAILURE_NON_RETRYABLE,
+    })
+    public @interface ObserverMitigationResult {
+        int MITIGATION_RESULT_UNKNOWN = 0;
+        int MITIGATION_RESULT_SUCCESS = 1;
+        int MITIGATION_RESULT_SKIPPED = 2;
+        int MITIGATION_RESULT_FAILURE_RETRYABLE = 3;
+        int MITIGATION_RESULT_FAILURE_NON_RETRYABLE = 4;
+    }
+
     /** Possible severity values of the user impact of a
      * {@link PackageHealthObserver#onExecuteHealthCheckMitigation}.
      * @hide
@@ -809,16 +873,25 @@
                 int mitigationCount);
 
         /**
-         * Executes mitigation for {@link #onHealthCheckFailed}.
+         * This would be called after {@link #onHealthCheckFailed}.
+         * This is called only if current observer returned least impact mitigation for failed
+         * health check.
          *
          * @param versionedPackage the package that is failing. This may be null if a native
-         *                          service is crashing.
-         * @param failureReason   the type of failure that is occurring.
+         *                         service is crashing.
+         * @param failureReason    the type of failure that is occurring.
          * @param mitigationCount the number of times mitigation has been called for this package
-         *                        (including this time).
-         * @return {@code true} if action was executed successfully, {@code false} otherwise
+         *                         (including this time).
+         * @return {@link #MITIGATION_RESULT_SUCCESS} if the mitigation was successful,
+         *         {@link #MITIGATION_RESULT_FAILURE_RETRYABLE} if the mitigation failed but can be
+         *         retried,
+         *         {@link #MITIGATION_RESULT_FAILURE_NON_RETRYABLE} if the mitigation failed and
+         *         cannot be retried,
+         *         {@link #MITIGATION_RESULT_UNKNOWN} if the result of the mitigation is unknown,
+         *         or {@link #MITIGATION_RESULT_SKIPPED} if the mitigation was skipped.
          */
-        boolean onExecuteHealthCheckMitigation(@Nullable VersionedPackage versionedPackage,
+        @ObserverMitigationResult int onExecuteHealthCheckMitigation(
+                @Nullable VersionedPackage versionedPackage,
                 @FailureReasons int failureReason, int mitigationCount);
 
 
@@ -834,12 +907,23 @@
         }
 
         /**
-         * Executes mitigation for {@link #onBootLoop}
+         * This would be called after {@link #onBootLoop}.
+         * This is called only if current observer returned least impact mitigation for fixing
+         * boot loop.
+         *
          * @param mitigationCount the number of times mitigation has been attempted for this
          *                        boot loop (including this time).
+         *
+         * @return {@link #MITIGATION_RESULT_SUCCESS} if the mitigation was successful,
+         *         {@link #MITIGATION_RESULT_FAILURE_RETRYABLE} if the mitigation failed but can be
+         *         retried,
+         *         {@link #MITIGATION_RESULT_FAILURE_NON_RETRYABLE} if the mitigation failed and
+         *         cannot be retried,
+         *         {@link #MITIGATION_RESULT_UNKNOWN} if the result of the mitigation is unknown,
+         *         or {@link #MITIGATION_RESULT_SKIPPED} if the mitigation was skipped.
          */
-        default boolean onExecuteBootLoopMitigation(int mitigationCount) {
-            return false;
+        default @ObserverMitigationResult int onExecuteBootLoopMitigation(int mitigationCount) {
+            return ObserverMitigationResult.MITIGATION_RESULT_SKIPPED;
         }
 
         // TODO(b/120598832): Ensure uniqueness?
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java
index 2bb72fb..c6452d3 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java
@@ -18,6 +18,8 @@
 
 import static android.provider.DeviceConfig.Properties;
 
+import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED;
+import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS;
 import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent;
 
 import android.annotation.IntDef;
@@ -859,10 +861,10 @@
         }
 
         @Override
-        public boolean onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage,
+        public int onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage,
                 @FailureReasons int failureReason, int mitigationCount) {
             if (isDisabled()) {
-                return false;
+                return MITIGATION_RESULT_SKIPPED;
             }
             Slog.i(TAG, "Executing remediation."
                     + " failedPackage: "
@@ -884,9 +886,9 @@
                 }
                 executeRescueLevel(mContext,
                         failedPackage == null ? null : failedPackage.getPackageName(), level);
-                return true;
+                return MITIGATION_RESULT_SUCCESS;
             } else {
-                return false;
+                return MITIGATION_RESULT_SKIPPED;
             }
         }
 
@@ -927,9 +929,9 @@
         }
 
         @Override
-        public boolean onExecuteBootLoopMitigation(int mitigationCount) {
+        public int onExecuteBootLoopMitigation(int mitigationCount) {
             if (isDisabled()) {
-                return false;
+                return MITIGATION_RESULT_SKIPPED;
             }
             boolean mayPerformReboot = !shouldThrottleReboot();
             final int level;
@@ -944,7 +946,7 @@
                 level = getRescueLevel(mitigationCount, mayPerformReboot);
             }
             executeRescueLevel(mContext, /*failedPackage=*/ null, level);
-            return true;
+            return MITIGATION_RESULT_SUCCESS;
         }
 
         @Override
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 0692cdb..0411537 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -18,6 +18,8 @@
 
 import static android.content.pm.Flags.provideInfoOfApkInApex;
 
+import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED;
+import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS;
 import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent;
 
 import android.annotation.AnyThread;
@@ -175,7 +177,7 @@
     }
 
     @Override
-    public boolean onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage,
+    public int onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage,
             @FailureReasons int rollbackReason, int mitigationCount) {
         Slog.i(TAG, "Executing remediation."
                 + " failedPackage: "
@@ -186,7 +188,7 @@
             List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
             if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
                 mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
-                return true;
+                return MITIGATION_RESULT_SUCCESS;
             }
 
             List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
@@ -201,7 +203,7 @@
         } else {
             if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
                 mHandler.post(() -> rollbackAll(rollbackReason));
-                return true;
+                return MITIGATION_RESULT_SUCCESS;
             }
 
             RollbackInfo rollback = getAvailableRollback(failedPackage);
@@ -213,7 +215,7 @@
         }
 
         // Assume rollbacks executed successfully
-        return true;
+        return MITIGATION_RESULT_SUCCESS;
     }
 
     @Override
@@ -229,15 +231,15 @@
     }
 
     @Override
-    public boolean onExecuteBootLoopMitigation(int mitigationCount) {
+    public int onExecuteBootLoopMitigation(int mitigationCount) {
         if (Flags.recoverabilityDetection()) {
             List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
 
             triggerLeastImpactLevelRollback(availableRollbacks,
                     PackageWatchdog.FAILURE_REASON_BOOT_LOOP);
-            return true;
+            return MITIGATION_RESULT_SUCCESS;
         }
-        return false;
+        return MITIGATION_RESULT_SKIPPED;
     }
 
     @Override
diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp
index f380e7f..81358ca 100644
--- a/packages/SettingsLib/tests/robotests/Android.bp
+++ b/packages/SettingsLib/tests/robotests/Android.bp
@@ -76,7 +76,6 @@
     tools: ["soong_zip"],
     cmd: "mkdir -p $(genDir)/META-INF/services/ && touch $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider &&" +
         "echo -e 'org.robolectric.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
-        "echo -e 'org.robolectric.shadows.multidex.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
         "echo -e 'org.robolectric.shadows.httpclient.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
         //"echo -e 'com.android.settings.testutils.shadow.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
         "echo -e 'com.android.settingslib.testutils.shadow.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 74c8710..1b1c91d 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -496,14 +496,14 @@
 }
 
 flag {
-    name: "status_bar_notification_chips_test"
+    name: "promote_notifications_automatically"
     namespace: "systemui"
-    description: "Flag to enable certain features that let us test the status bar notification "
-        "chips with teamfooders. This flag should *never* be released to trunkfood or nextfood."
-    bug: "361346412"
+    description: "Flag to automatically turn certain notifications into promoted notifications so "
+        " we can test promoted notifications with teamfooders. This flag should *never* be released "
+        "to trunkfood or nextfood."
+    bug: "367705002"
 }
 
-
 flag {
     name: "compose_bouncer"
     namespace: "systemui"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
index ead151e..b069855 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
@@ -106,7 +106,7 @@
             repository.setKeyguardEnabled(true)
             runCurrent()
 
-            assertEquals(listOf(false, true, false), canWake)
+            assertEquals(listOf(false, true), canWake)
         }
 
     @Test
@@ -374,6 +374,8 @@
                     // Should be canceled by the wakeup, but there would still be an
                     // alarm in flight that should be canceled.
                     false,
+                    // True once we're actually GONE.
+                    true,
                 ),
                 canWake,
             )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
index df4d5ab..ea2b3cd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
@@ -206,6 +206,9 @@
     @Test
     @RequiresFlagsDisabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING)
     fun setSurfaceBehindVisibility_falseSetsLockscreenVisibility_without_keyguard_shell_transitions() {
+        // Show the surface behind, then hide it.
+        underTest.setLockscreenShown(true)
+        underTest.setSurfaceBehindVisibility(true)
         underTest.setSurfaceBehindVisibility(false)
         verify(activityTaskManagerService).setLockScreenShown(eq(true), any())
     }
@@ -213,6 +216,9 @@
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING)
     fun setSurfaceBehindVisibility_falseSetsLockscreenVisibility_with_keyguard_shell_transitions() {
+        // Show the surface behind, then hide it.
+        underTest.setLockscreenShown(true)
+        underTest.setSurfaceBehindVisibility(true)
         underTest.setSurfaceBehindVisibility(false)
         verify(keyguardTransitions).startKeyguardTransition(eq(true), any())
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt
index 6ec38ba..77be8c7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt
@@ -16,9 +16,8 @@
 
 package com.android.systemui.mediarouter.data.repository
 
-import android.media.projection.StopReason
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
@@ -102,7 +101,7 @@
                 origin = CastDevice.CastOrigin.MediaRouter,
             )
 
-        underTest.stopCasting(device, StopReason.STOP_UNKNOWN)
+        underTest.stopCasting(device)
 
         assertThat(castController.lastStoppedDevice).isEqualTo(device)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 165ff7b..090e2e9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.dump.nano.SystemUIProtoDump
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.qs.QSTile.BooleanState
+import com.android.systemui.plugins.qs.TileDetailsViewModel
 import com.android.systemui.qs.FakeQSFactory
 import com.android.systemui.qs.FakeQSTile
 import com.android.systemui.qs.external.CustomTile
@@ -154,7 +155,7 @@
                     TileSpec.create("e"),
                     CUSTOM_TILE_SPEC,
                     TileSpec.create("d"),
-                    TileSpec.create("non_existent")
+                    TileSpec.create("non_existent"),
                 )
             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
 
@@ -190,11 +191,7 @@
     @Test
     fun logTileCreated() =
         testScope.runTest(USER_INFO_0) {
-            val specs =
-                listOf(
-                    TileSpec.create("a"),
-                    CUSTOM_TILE_SPEC,
-                )
+            val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
             runCurrent()
 
@@ -204,10 +201,7 @@
     @Test
     fun logTileNotFoundInFactory() =
         testScope.runTest(USER_INFO_0) {
-            val specs =
-                listOf(
-                    TileSpec.create("non_existing"),
-                )
+            val specs = listOf(TileSpec.create("non_existing"))
             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
             runCurrent()
 
@@ -218,10 +212,7 @@
     @Test
     fun tileNotAvailableDestroyed_logged() =
         testScope.runTest(USER_INFO_0) {
-            val specs =
-                listOf(
-                    TileSpec.create("e"),
-                )
+            val specs = listOf(TileSpec.create("e"))
             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
             runCurrent()
 
@@ -229,7 +220,7 @@
             verify(logger)
                 .logTileDestroyed(
                     specs[0],
-                    QSPipelineLogger.TileDestroyedReason.NEW_TILE_NOT_AVAILABLE
+                    QSPipelineLogger.TileDestroyedReason.NEW_TILE_NOT_AVAILABLE,
                 )
         }
 
@@ -238,11 +229,7 @@
         testScope.runTest(USER_INFO_0) {
             val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
 
-            val specs =
-                listOf(
-                    TileSpec.create("a"),
-                    TileSpec.create("e"),
-                )
+            val specs = listOf(TileSpec.create("a"), TileSpec.create("e"))
             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
 
             assertThat(tiles).isEqualTo(listOf(TileSpec.create("a")))
@@ -266,10 +253,7 @@
         testScope.runTest(USER_INFO_0) {
             val tiles by collectLastValue(underTest.currentTiles)
 
-            val specs =
-                listOf(
-                    TileSpec.create("a"),
-                )
+            val specs = listOf(TileSpec.create("a"))
 
             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
             val originalTileA = tiles!![0].tile
@@ -299,7 +283,7 @@
             verify(logger)
                 .logTileDestroyed(
                     TileSpec.create("c"),
-                    QSPipelineLogger.TileDestroyedReason.TILE_REMOVED
+                    QSPipelineLogger.TileDestroyedReason.TILE_REMOVED,
                 )
         }
 
@@ -325,7 +309,7 @@
             verify(logger)
                 .logTileDestroyed(
                     TileSpec.create("a"),
-                    QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE
+                    QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE,
                 )
 
             assertThat(tiles?.size).isEqualTo(1)
@@ -370,7 +354,7 @@
             verify(logger)
                 .logTileDestroyed(
                     specs0[0],
-                    QSPipelineLogger.TileDestroyedReason.TILE_NOT_PRESENT_IN_NEW_USER
+                    QSPipelineLogger.TileDestroyedReason.TILE_NOT_PRESENT_IN_NEW_USER,
                 )
         }
 
@@ -418,21 +402,12 @@
         testScope.runTest(USER_INFO_0) {
             val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
             val spec = TileSpec.create("a")
-            val currentSpecs =
-                listOf(
-                    TileSpec.create("b"),
-                    TileSpec.create("c"),
-                )
+            val currentSpecs = listOf(TileSpec.create("b"), TileSpec.create("c"))
             tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
 
             underTest.addTile(spec, position = 1)
 
-            val expectedSpecs =
-                listOf(
-                    TileSpec.create("b"),
-                    spec,
-                    TileSpec.create("c"),
-                )
+            val expectedSpecs = listOf(TileSpec.create("b"), spec, TileSpec.create("c"))
             assertThat(tiles).isEqualTo(expectedSpecs)
         }
 
@@ -442,11 +417,7 @@
             val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
             val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id))
             val spec = TileSpec.create("a")
-            val currentSpecs =
-                listOf(
-                    TileSpec.create("b"),
-                    TileSpec.create("c"),
-                )
+            val currentSpecs = listOf(TileSpec.create("b"), TileSpec.create("c"))
             tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
             tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs)
 
@@ -455,12 +426,7 @@
 
             assertThat(tiles0).isEqualTo(currentSpecs)
 
-            val expectedSpecs =
-                listOf(
-                    TileSpec.create("b"),
-                    spec,
-                    TileSpec.create("c"),
-                )
+            val expectedSpecs = listOf(TileSpec.create("b"), spec, TileSpec.create("c"))
             assertThat(tiles1).isEqualTo(expectedSpecs)
         }
 
@@ -515,11 +481,7 @@
             val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
             val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id))
             val currentSpecs =
-                listOf(
-                    TileSpec.create("a"),
-                    TileSpec.create("b"),
-                    TileSpec.create("c"),
-                )
+                listOf(TileSpec.create("a"), TileSpec.create("b"), TileSpec.create("c"))
             tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
             tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs)
 
@@ -557,11 +519,7 @@
             tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
             runCurrent()
 
-            val newSpecs =
-                listOf(
-                    otherCustomTileSpec,
-                    TileSpec.create("a"),
-                )
+            val newSpecs = listOf(otherCustomTileSpec, TileSpec.create("a"))
 
             underTest.setTiles(newSpecs)
             runCurrent()
@@ -615,7 +573,7 @@
 
             tileSpecRepository.setTiles(
                 USER_INFO_0.id,
-                listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
+                listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC),
             )
             val newTileA = tiles!![0].tile
             assertThat(tileA).isSameInstanceAs(newTileA)
@@ -650,7 +608,7 @@
 
             installedTilesPackageRepository.setInstalledPackagesForUser(
                 USER_INFO_0.id,
-                setOf(TEST_COMPONENT)
+                setOf(TEST_COMPONENT),
             )
 
             assertThat(tiles!!.size).isEqualTo(3)
@@ -676,10 +634,7 @@
     @Test
     fun changeInPackagesTiles_doesntTriggerUserChange_logged() =
         testScope.runTest(USER_INFO_0) {
-            val specs =
-                listOf(
-                    TileSpec.create("a"),
-                )
+            val specs = listOf(TileSpec.create("a"))
             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
             runCurrent()
             // Settled on the same list of tiles.
@@ -691,7 +646,6 @@
             verify(logger, never()).logTileUserChanged(TileSpec.create("a"), 0)
         }
 
-
     @Test
     fun getTileDetails() =
         testScope.runTest(USER_INFO_0) {
@@ -711,11 +665,19 @@
             assertThat(tiles!![2].spec).isEqualTo(tileNoDetails)
             (tiles!![2].tile as FakeQSTile).hasDetailsViewModel = false
 
-            assertThat(tiles!![0].tile.detailsViewModel.getTitle()).isEqualTo("a")
-            assertThat(tiles!![1].tile.detailsViewModel.getTitle()).isEqualTo("b")
-            assertThat(tiles!![2].tile.detailsViewModel).isNull()
-        }
+            var currentModel: TileDetailsViewModel? = null
+            val setCurrentModel = { model: TileDetailsViewModel? -> currentModel = model }
+            tiles!![0].tile.getDetailsViewModel(setCurrentModel)
+            assertThat(currentModel?.getTitle()).isEqualTo("a")
 
+            currentModel = null
+            tiles!![1].tile.getDetailsViewModel(setCurrentModel)
+            assertThat(currentModel?.getTitle()).isEqualTo("b")
+
+            currentModel = null
+            tiles!![2].tile.getDetailsViewModel(setCurrentModel)
+            assertThat(currentModel).isNull()
+        }
 
     private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) {
         this.state = state
@@ -745,7 +707,7 @@
                     customTileAddedRepository.setTileAdded(
                         CUSTOM_TILE_SPEC.componentName,
                         currentUser,
-                        true
+                        true,
                     )
                 }
             in VALID_TILES -> FakeQSTile(currentUser, available = spec !in unavailableTiles)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java
index 31a627f..9f12b18 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -20,7 +20,6 @@
 import static junit.framework.TestCase.assertEquals;
 
 import static org.junit.Assert.assertFalse;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
@@ -31,7 +30,6 @@
 import android.media.MediaRouter;
 import android.media.MediaRouter.RouteInfo;
 import android.media.projection.MediaProjectionInfo;
-import android.media.projection.StopReason;
 import android.os.Handler;
 import android.service.quicksettings.Tile;
 import android.testing.TestableLooper;
@@ -338,8 +336,7 @@
         mCastTile.handleClick(null /* view */);
         mTestableLooper.processAllMessages();
 
-        verify(mController, times(1))
-                .stopCasting(same(device), eq(StopReason.STOP_QS_TILE));
+        verify(mController, times(1)).stopCasting(same(device));
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index fc1d73b..0fd7c98 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -27,13 +27,11 @@
 import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Dialog;
-import android.media.projection.StopReason;
 import android.os.Handler;
 import android.platform.test.flag.junit.FlagsParameterization;
 import android.service.quicksettings.Tile;
@@ -236,7 +234,7 @@
 
         mTile.handleClick(null /* view */);
 
-        verify(mController, times(1)).stopRecording(eq(StopReason.STOP_QS_TILE));
+        verify(mController, times(1)).stopRecording();
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt
index 778c73f..0b56d7b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.qs.tiles.impl.screenrecord.domain.interactor
 
 import android.app.Dialog
-import android.media.projection.StopReason
 import android.os.UserHandle
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -93,7 +92,7 @@
 
         underTest.handleInput(QSTileInputTestKtx.click(recordingModel))
 
-        verify(recordingController).stopRecording(eq(StopReason.STOP_QS_TILE))
+        verify(recordingController).stopRecording()
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index 50fa9d2..a6a1d4a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -41,7 +41,6 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.Intent;
-import android.media.projection.StopReason;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -200,16 +199,16 @@
     public void testOnSystemRequestedStop_recordingInProgress_endsRecording() throws IOException {
         doReturn(true).when(mController).isRecording();
 
-        mRecordingService.onStopped(StopReason.STOP_UNKNOWN);
+        mRecordingService.onStopped();
 
-        verify(mScreenMediaRecorder).end(eq(StopReason.STOP_UNKNOWN));
+        verify(mScreenMediaRecorder).end();
     }
 
     @Test
     public void testOnSystemRequestedStop_recordingInProgress_updatesState() {
         doReturn(true).when(mController).isRecording();
 
-        mRecordingService.onStopped(StopReason.STOP_UNKNOWN);
+        mRecordingService.onStopped();
 
         assertUpdateState(false);
     }
@@ -219,18 +218,18 @@
             throws IOException {
         doReturn(false).when(mController).isRecording();
 
-        mRecordingService.onStopped(StopReason.STOP_UNKNOWN);
+        mRecordingService.onStopped();
 
-        verify(mScreenMediaRecorder, never()).end(StopReason.STOP_UNKNOWN);
+        verify(mScreenMediaRecorder, never()).end();
     }
 
     @Test
     public void testOnSystemRequestedStop_recorderEndThrowsRuntimeException_releasesRecording()
             throws IOException {
         doReturn(true).when(mController).isRecording();
-        doThrow(new RuntimeException()).when(mScreenMediaRecorder).end(StopReason.STOP_UNKNOWN);
+        doThrow(new RuntimeException()).when(mScreenMediaRecorder).end();
 
-        mRecordingService.onStopped(StopReason.STOP_UNKNOWN);
+        mRecordingService.onStopped();
 
         verify(mScreenMediaRecorder).release();
     }
@@ -239,7 +238,7 @@
     public void testOnSystemRequestedStop_whenRecordingInProgress_showsNotifications() {
         doReturn(true).when(mController).isRecording();
 
-        mRecordingService.onStopped(StopReason.STOP_UNKNOWN);
+        mRecordingService.onStopped();
 
         // Processing notification
         ArgumentCaptor<Notification> notifCaptor = ArgumentCaptor.forClass(Notification.class);
@@ -272,9 +271,9 @@
     public void testOnSystemRequestedStop_recorderEndThrowsRuntimeException_showsErrorNotification()
             throws IOException {
         doReturn(true).when(mController).isRecording();
-        doThrow(new RuntimeException()).when(mScreenMediaRecorder).end(anyInt());
+        doThrow(new RuntimeException()).when(mScreenMediaRecorder).end();
 
-        mRecordingService.onStopped(StopReason.STOP_UNKNOWN);
+        mRecordingService.onStopped();
 
         verify(mRecordingService).createErrorSavingNotification(any());
         ArgumentCaptor<Notification> notifCaptor = ArgumentCaptor.forClass(Notification.class);
@@ -290,9 +289,9 @@
     public void testOnSystemRequestedStop_recorderEndThrowsOOMError_releasesRecording()
             throws IOException {
         doReturn(true).when(mController).isRecording();
-        doThrow(new OutOfMemoryError()).when(mScreenMediaRecorder).end(anyInt());
+        doThrow(new OutOfMemoryError()).when(mScreenMediaRecorder).end();
 
-        assertThrows(Throwable.class, () -> mRecordingService.onStopped(StopReason.STOP_UNKNOWN));
+        assertThrows(Throwable.class, () -> mRecordingService.onStopped());
 
         verify(mScreenMediaRecorder).release();
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt
index ade5941..aceea90 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.screenrecord.data.repository
 
-import android.media.projection.StopReason
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -32,7 +31,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.argumentCaptor
-import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
@@ -128,8 +126,8 @@
     @Test
     fun stopRecording_invokesController() =
         testScope.runTest {
-            underTest.stopRecording(StopReason.STOP_PRIVACY_CHIP)
+            underTest.stopRecording()
 
-            verify(recordingController).stopRecording(eq(StopReason.STOP_PRIVACY_CHIP))
+            verify(recordingController).stopRecording()
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureRecognizerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureRecognizerTest.kt
index 533665e..8972f3e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureRecognizerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureRecognizerTest.kt
@@ -83,13 +83,40 @@
     }
 
     @Test
-    fun doesntTriggerGestureFinished_onTwoFingersSwipe() {
-        assertStateAfterEvents(events = TwoFingerGesture.swipeRight(), expectedState = NotStarted)
+    fun triggersGestureError_onTwoFingersSwipe() {
+        assertStateAfterEvents(events = TwoFingerGesture.swipeRight(), expectedState = Error)
     }
 
     @Test
-    fun doesntTriggerGestureFinished_onFourFingersSwipe() {
-        assertStateAfterEvents(events = FourFingerGesture.swipeRight(), expectedState = NotStarted)
+    fun doesntTriggerGestureError_TwoFingerSwipeInProgress() {
+        assertStateAfterEvents(
+            events = TwoFingerGesture.eventsForGestureInProgress { move(deltaX = SWIPE_DISTANCE) },
+            expectedState = NotStarted,
+        )
+    }
+
+    @Test
+    fun triggersGestureError_onFourFingersSwipe() {
+        assertStateAfterEvents(events = FourFingerGesture.swipeRight(), expectedState = Error)
+    }
+
+    @Test
+    fun doesntTriggerGestureError_FourFingerSwipeInProgress() {
+        assertStateAfterEvents(
+            events = FourFingerGesture.eventsForGestureInProgress { move(deltaX = SWIPE_DISTANCE) },
+            expectedState = NotStarted,
+        )
+    }
+
+    @Test
+    fun ignoresOneFingerSwipes() {
+        val oneFingerSwipe =
+            listOf(
+                touchpadEvent(MotionEvent.ACTION_DOWN, 50f, 50f),
+                touchpadEvent(MotionEvent.ACTION_MOVE, 55f, 55f),
+                touchpadEvent(MotionEvent.ACTION_UP, 60f, 60f),
+            )
+        assertStateAfterEvents(events = oneFingerSwipe, expectedState = NotStarted)
     }
 
     private fun assertStateAfterEvents(events: List<MotionEvent>, expectedState: GestureState) {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 322da32..56176cf 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -33,6 +33,7 @@
 import com.android.systemui.plugins.qs.QSTile.State;
 
 import java.util.Objects;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 @ProvidesInterface(version = QSTile.VERSION)
@@ -122,9 +123,32 @@
     boolean isListening();
 
     /**
-     * Return this tile's {@link TileDetailsViewModel} to be used to render the TileDetailsView.
+     * Get this tile's {@link TileDetailsViewModel} through a callback.
+     *
+     * Please only override this method if the tile can't get its {@link TileDetailsViewModel}
+     * synchronously and thus need a callback to defer it.
+     *
+     * @return a boolean indicating whether this tile has a {@link TileDetailsViewModel}. The tile's
+     * {@link TileDetailsViewModel} will be passed to the callback. Please always return true when
+     * overriding this method. Return false will make the tile display its dialog instead of details
+     * view, and it will not wait for the callback to be returned before proceeding to show the
+     * dialog.
      */
-    default TileDetailsViewModel getDetailsViewModel() { return null; }
+    default boolean getDetailsViewModel(Consumer<TileDetailsViewModel> callback) {
+        TileDetailsViewModel tileDetailsViewModel = getDetailsViewModel();
+        callback.accept(tileDetailsViewModel);
+        return tileDetailsViewModel != null;
+    }
+
+    /**
+     * Return this tile's {@link TileDetailsViewModel} to be used to render the TileDetailsView.
+     *
+     * Please only override this method if the tile doesn't need a callback to set its
+     * {@link TileDetailsViewModel}.
+     */
+    default TileDetailsViewModel getDetailsViewModel() {
+        return null;
+    }
 
     @ProvidesInterface(version = Callback.VERSION)
     interface Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt
new file mode 100644
index 0000000..9dd3b6d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.bluetooth.qsdialog
+
+import android.view.LayoutInflater
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.systemui.plugins.qs.TileDetailsViewModel
+import com.android.systemui.res.R
+
+class BluetoothDetailsViewModel(onLongClick: () -> Unit) : TileDetailsViewModel() {
+    private val _onLongClick = onLongClick
+
+    @Composable
+    override fun GetContentView() {
+        AndroidView(
+            modifier = Modifier.fillMaxWidth().fillMaxHeight(),
+            factory = { context ->
+                // Inflate with the existing dialog xml layout
+                LayoutInflater.from(context).inflate(R.layout.bluetooth_tile_dialog, null)
+                // TODO: b/378513956 - Implement the bluetooth details view
+            },
+        )
+    }
+
+    override fun clickOnSettingsButton() {
+        _onLongClick()
+    }
+
+    override fun getTitle(): String {
+        // TODO: b/378513956 Update the placeholder text
+        return "Bluetooth"
+    }
+
+    override fun getSubTitle(): String {
+        // TODO: b/378513956 Update the placeholder text
+        return "Tap to connect or disconnect a device"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
index a137d6c..5b28a3f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -131,8 +131,18 @@
             Log.d(TAG, "ActivityTaskManagerService#keyguardGoingAway()")
             activityTaskManagerService.keyguardGoingAway(0)
             isKeyguardGoingAway = true
-        } else {
-            // Hide the surface by setting the lockscreen showing.
+        } else if (isLockscreenShowing == true) {
+            // Re-show the lockscreen if the surface was visible and we want to make it invisible,
+            // and the lockscreen is currently showing (this is the usual case of the going away
+            // animation). Re-showing the lockscreen will cancel the going away animation. If we
+            // want to hide the surface, but the lockscreen is not currently showing, do nothing and
+            // wait for lockscreenVisibility to emit if it's appropriate to show the lockscreen (it
+            // might be disabled/suppressed).
+            Log.d(
+                TAG,
+                "setLockscreenShown(true) because we're setting the surface invisible " +
+                    "and lockscreen is already showing.",
+            )
             setLockscreenShown(true)
         }
     }
@@ -153,6 +163,10 @@
         nonApps: Array<RemoteAnimationTarget>,
         finishedCallback: IRemoteAnimationFinishedCallback,
     ) {
+        // Make sure this is true - we set it true when requesting keyguardGoingAway, but there are
+        // cases where WM starts this transition on its own.
+        isKeyguardGoingAway = true
+
         // Ensure that we've started a dismiss keyguard transition. WindowManager can start the
         // going away animation on its own, if an activity launches and then requests dismissing the
         // keyguard. In this case, this is the first and only signal we'll receive to start
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
index 8641dfa..a133f06 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
@@ -90,6 +90,7 @@
     private val selectedUserInteractor: SelectedUserInteractor,
     keyguardEnabledInteractor: KeyguardEnabledInteractor,
     keyguardServiceLockNowInteractor: KeyguardServiceLockNowInteractor,
+    keyguardInteractor: KeyguardInteractor,
 ) {
 
     /**
@@ -106,13 +107,18 @@
             .onStart { emit(false) }
 
     /**
-     * Whether we can wake from AOD/DOZING directly to GONE, bypassing LOCKSCREEN/BOUNCER states.
+     * Whether we can wake from AOD/DOZING or DREAMING directly to GONE, bypassing
+     * LOCKSCREEN/BOUNCER states.
      *
      * This is possible in the following cases:
      * - Keyguard is disabled, either from an app request or from security being set to "None".
      * - Keyguard is suppressed, via adb locksettings.
      * - We're wake and unlocking (fingerprint auth occurred while asleep).
      * - We're allowed to ignore auth and return to GONE, due to timeouts not elapsing.
+     * - We're DREAMING and dismissible.
+     * - We're already GONE. Technically you're already awake when GONE, but this makes it easier to
+     *   reason about this state (for example, if canWakeDirectlyToGone, don't tell WM to pause the
+     *   top activity - something you should never do while GONE as well).
      */
     val canWakeDirectlyToGone =
         combine(
@@ -120,14 +126,19 @@
                 shouldSuppressKeyguard,
                 repository.biometricUnlockState,
                 repository.canIgnoreAuthAndReturnToGone,
+                transitionInteractor.currentKeyguardState,
             ) {
                 keyguardEnabled,
                 shouldSuppressKeyguard,
                 biometricUnlockState,
-                canIgnoreAuthAndReturnToGone ->
+                canIgnoreAuthAndReturnToGone,
+                currentState ->
                 (!keyguardEnabled || shouldSuppressKeyguard) ||
                     BiometricUnlockMode.isWakeAndUnlock(biometricUnlockState.mode) ||
-                    canIgnoreAuthAndReturnToGone
+                    canIgnoreAuthAndReturnToGone ||
+                    (currentState == KeyguardState.DREAMING &&
+                        keyguardInteractor.isKeyguardDismissible.value) ||
+                    currentState == KeyguardState.GONE
             }
             .distinctUntilChanged()
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index 3b99bb4..184f302 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -34,7 +34,7 @@
 import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
-import com.android.systemui.util.kotlin.Utils.Companion.toTriple
+import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.sample
 import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import dagger.Lazy
@@ -240,10 +240,11 @@
         combine(
                 transitionInteractor.currentKeyguardState,
                 wakeToGoneInteractor.canWakeDirectlyToGone,
-                ::Pair,
+                surfaceBehindVisibility,
+                ::Triple,
             )
-            .sample(transitionInteractor.startedStepWithPrecedingStep, ::toTriple)
-            .map { (currentState, canWakeDirectlyToGone, startedWithPrev) ->
+            .sample(transitionInteractor.startedStepWithPrecedingStep, ::toQuad)
+            .map { (currentState, canWakeDirectlyToGone, surfaceBehindVis, startedWithPrev) ->
                 val startedFromStep = startedWithPrev.previousValue
                 val startedStep = startedWithPrev.newValue
                 val returningToGoneAfterCancellation =
@@ -296,6 +297,11 @@
                     // we should simply tell WM that the lockscreen is no longer visible, and
                     // *not* play the going away animation or related animations.
                     false
+                } else if (!surfaceBehindVis) {
+                    // If the surface behind is not visible, then the lockscreen has to be visible
+                    // since there's nothing to show. The surface behind will never be invisible if
+                    // the lockscreen is disabled or suppressed.
+                    true
                 } else {
                     currentState != KeyguardState.GONE
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt b/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
index a19c9b3..debb667 100644
--- a/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.mediarouter.data.repository
 
-import android.media.projection.StopReason
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.LogBuffer
@@ -41,7 +40,7 @@
     val castDevices: StateFlow<List<CastDevice>>
 
     /** Stops the cast to the given device. */
-    fun stopCasting(device: CastDevice, @StopReason stopReason: Int)
+    fun stopCasting(device: CastDevice)
 }
 
 @SysUISingleton
@@ -68,8 +67,8 @@
             .map { it.filter { device -> device.origin == CastDevice.CastOrigin.MediaRouter } }
             .stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
 
-    override fun stopCasting(device: CastDevice, @StopReason stopReason: Int) {
-        castController.stopCasting(device, stopReason)
+    override fun stopCasting(device: CastDevice) {
+        castController.stopCasting(device)
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt
index d8361f5..03f0297 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt
@@ -23,11 +23,9 @@
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
 
 @SysUISingleton
-class DetailsViewModel
-@Inject constructor(val currentTilesInteractor: CurrentTilesInteractor) {
+class DetailsViewModel @Inject constructor(val currentTilesInteractor: CurrentTilesInteractor) {
 
     /**
      * The current active [TileDetailsViewModel]. If it's `null`, it means the qs overlay is not
@@ -38,6 +36,7 @@
 
     /**
      * Update the active [TileDetailsViewModel] to `null`.
+     *
      * @see activeTileDetails
      */
     fun closeDetailedView() {
@@ -45,8 +44,9 @@
     }
 
     /**
-     * Update the active [TileDetailsViewModel] to the `spec`'s corresponding view model.
-     * Return if the [TileDetailsViewModel] is successfully found.
+     * Update the active [TileDetailsViewModel] to the `spec`'s corresponding view model. Return if
+     * the [TileDetailsViewModel] is successfully handled.
+     *
      * @see activeTileDetails
      */
     fun onTileClicked(spec: TileSpec?): Boolean {
@@ -55,11 +55,11 @@
             return false
         }
 
-        _activeTileDetails.value = currentTilesInteractor
-            .currentQSTiles
-            .firstOrNull { it.tileSpec == spec.spec }
-            ?.detailsViewModel
+        val currentTile =
+            currentTilesInteractor.currentQSTiles.firstOrNull { it.tileSpec == spec.spec }
 
-        return _activeTileDetails.value != null
+        return currentTile?.getDetailsViewModel { detailsViewModel ->
+            _activeTileDetails.value = detailsViewModel
+        } ?: false
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 7eb0aaa..0109e70a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -43,6 +43,7 @@
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.satellite.SatelliteDialogUtils;
 import com.android.systemui.animation.Expandable;
+import com.android.systemui.bluetooth.qsdialog.BluetoothDetailsViewModel;
 import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -51,6 +52,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.plugins.qs.TileDetailsViewModel;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
@@ -63,6 +65,7 @@
 
 import java.util.List;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 import javax.inject.Inject;
 
@@ -121,6 +124,21 @@
 
     @Override
     protected void handleClick(@Nullable Expandable expandable) {
+        handleClickWithSatelliteCheck(() -> handleClickEvent(expandable));
+    }
+
+    @Override
+    public boolean getDetailsViewModel(Consumer<TileDetailsViewModel> callback) {
+        handleClickWithSatelliteCheck(() ->
+                callback.accept(new BluetoothDetailsViewModel(() -> {
+                    longClick(null);
+                    return null;
+                }))
+        );
+        return true;
+    }
+
+    private void handleClickWithSatelliteCheck(Runnable clickCallback) {
         if (com.android.internal.telephony.flags.Flags.oemEnabledSatelliteFlag()) {
             if (mClickJob != null && !mClickJob.isCompleted()) {
                 return;
@@ -130,12 +148,12 @@
                         if (!isAllowClick) {
                             return null;
                         }
-                        handleClickEvent(expandable);
+                        clickCallback.run();
                         return null;
                     });
             return;
         }
-        handleClickEvent(expandable);
+        clickCallback.run();
     }
 
     private void handleClickEvent(@Nullable Expandable expandable) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 30c2adf..ad027b4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -24,7 +24,6 @@
 import android.app.Dialog;
 import android.content.Intent;
 import android.media.MediaRouter.RouteInfo;
-import android.media.projection.StopReason;
 import android.os.Handler;
 import android.os.Looper;
 import android.provider.Settings;
@@ -184,7 +183,7 @@
                 });
             }
         } else {
-            mController.stopCasting(activeDevices.get(0), StopReason.STOP_QS_TILE);
+            mController.stopCasting(activeDevices.get(0));
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index ec8d30b..fc82592 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -18,7 +18,6 @@
 
 import android.app.Dialog;
 import android.content.Intent;
-import android.media.projection.StopReason;
 import android.os.Handler;
 import android.os.Looper;
 import android.service.quicksettings.Tile;
@@ -227,7 +226,7 @@
     }
 
     private void stopRecording() {
-        mController.stopRecording(StopReason.STOP_QS_TILE);
+        mController.stopRecording();
     }
 
     private final class Callback implements RecordingController.RecordingStateChangeCallback {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt
index 9453447..85aa674 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.qs.tiles.impl.screenrecord.domain.interactor
 
-import android.media.projection.StopReason
 import android.util.Log
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.animation.DialogCuj
@@ -62,9 +61,7 @@
                             Log.d(TAG, "Cancelling countdown")
                             withContext(backgroundContext) { recordingController.cancelCountdown() }
                         }
-                        is ScreenRecordModel.Recording -> {
-                            screenRecordRepository.stopRecording(StopReason.STOP_QS_TILE)
-                        }
+                        is ScreenRecordModel.Recording -> screenRecordRepository.stopRecording()
                         is ScreenRecordModel.DoingNothing ->
                             withContext(mainContext) {
                                 showPrompt(action.expandable, user.identifier)
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 9ee99e4..d7463f8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -23,7 +23,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.media.projection.StopReason;
 import android.os.Bundle;
 import android.os.CountDownTimer;
 import android.os.Process;
@@ -59,7 +58,6 @@
     private boolean mIsStarting;
     private boolean mIsRecording;
     private PendingIntent mStopIntent;
-    private @StopReason int mStopReason = StopReason.STOP_UNKNOWN;
     private final Bundle mInteractiveBroadcastOption;
     private CountDownTimer mCountDownTimer = null;
     private final Executor mMainExecutor;
@@ -85,7 +83,7 @@
             new UserTracker.Callback() {
                 @Override
                 public void onUserChanged(int newUser, @NonNull Context userContext) {
-                    stopRecording(StopReason.STOP_USER_SWITCH);
+                    stopRecording();
                 }
             };
 
@@ -242,11 +240,9 @@
     }
 
     /**
-     * Stop the recording and sets the stop reason to be used by the RecordingService
-     * @param stopReason the method of the recording stopped (i.e. QS tile, status bar chip, etc.)
+     * Stop the recording
      */
-    public void stopRecording(@StopReason int stopReason) {
-        mStopReason = stopReason;
+    public void stopRecording() {
         try {
             if (mStopIntent != null) {
                 mRecordingControllerLogger.logRecordingStopped();
@@ -281,10 +277,6 @@
         }
     }
 
-    public @StopReason int getStopReason() {
-        return mStopReason;
-    }
-
     @Override
     public void addCallback(@NonNull RecordingStateChangeCallback listener) {
         mListeners.add(listener);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index f7b5271..8c207d1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -26,7 +26,6 @@
 import android.content.Intent;
 import android.graphics.drawable.Icon;
 import android.media.MediaRecorder;
-import android.media.projection.StopReason;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -79,7 +78,6 @@
     private static final String EXTRA_SHOW_TAPS = "extra_showTaps";
     private static final String EXTRA_CAPTURE_TARGET = "extra_captureTarget";
     private static final String EXTRA_DISPLAY_ID = "extra_displayId";
-    private static final String EXTRA_STOP_REASON = "extra_stopReason";
 
     protected static final String ACTION_START = "com.android.systemui.screenrecord.START";
     protected static final String ACTION_SHOW_START_NOTIF =
@@ -244,8 +242,7 @@
                 // Check user ID - we may be getting a stop intent after user switch, in which case
                 // we want to post the notifications for that user, which is NOT current user
                 int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_ID_NOT_SPECIFIED);
-                int stopReason = intent.getIntExtra(EXTRA_STOP_REASON, mController.getStopReason());
-                stopService(userId, stopReason);
+                stopService(userId);
                 break;
 
             case ACTION_SHARE:
@@ -489,11 +486,11 @@
                 getTag(), notificationIdForGroup, groupNotif, currentUser);
     }
 
-    private void stopService(@StopReason int stopReason) {
-        stopService(USER_ID_NOT_SPECIFIED, stopReason);
+    private void stopService() {
+        stopService(USER_ID_NOT_SPECIFIED);
     }
 
-    private void stopService(int userId, @StopReason int stopReason) {
+    private void stopService(int userId) {
         if (userId == USER_ID_NOT_SPECIFIED) {
             userId = mUserContextTracker.getUserContext().getUserId();
         }
@@ -502,7 +499,7 @@
         setTapsVisible(mOriginalShowTaps);
         try {
             if (getRecorder() != null) {
-                getRecorder().end(stopReason);
+                getRecorder().end();
             }
             saveRecording(userId);
         } catch (RuntimeException exception) {
@@ -601,8 +598,7 @@
      * @return
      */
     protected Intent getNotificationIntent(Context context) {
-        return new Intent(context, this.getClass()).setAction(ACTION_STOP_NOTIF)
-                .putExtra(EXTRA_STOP_REASON, StopReason.STOP_HOST_APP);
+        return new Intent(context, this.getClass()).setAction(ACTION_STOP_NOTIF);
     }
 
     private Intent getShareIntent(Context context, Uri path) {
@@ -614,17 +610,14 @@
     @Override
     public void onInfo(MediaRecorder mr, int what, int extra) {
         Log.d(getTag(), "Media recorder info: " + what);
-        // Stop due to record reaching size limits so log as stopping due to error
-        Intent stopIntent = getStopIntent(this);
-        stopIntent.putExtra(EXTRA_STOP_REASON, StopReason.STOP_ERROR);
-        onStartCommand(stopIntent, 0, 0);
+        onStartCommand(getStopIntent(this), 0, 0);
     }
 
     @Override
-    public void onStopped(@StopReason int stopReason) {
+    public void onStopped() {
         if (mController.isRecording()) {
             Log.d(getTag(), "Stopping recording because the system requested the stop");
-            stopService(stopReason);
+            stopService();
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index f4455bf..2ca0621 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -41,7 +41,6 @@
 import android.media.projection.IMediaProjectionManager;
 import android.media.projection.MediaProjection;
 import android.media.projection.MediaProjectionManager;
-import android.media.projection.StopReason;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
@@ -301,7 +300,7 @@
     /**
      * End screen recording, throws an exception if stopping recording failed
      */
-    void end(@StopReason int stopReason) throws IOException {
+    void end() throws IOException {
         Closer closer = new Closer();
 
         // MediaRecorder might throw RuntimeException if stopped immediately after starting
@@ -310,17 +309,7 @@
         closer.register(mMediaRecorder::release);
         closer.register(mInputSurface::release);
         closer.register(mVirtualDisplay::release);
-        closer.register(() -> {
-            if (stopReason == StopReason.STOP_UNKNOWN) {
-                // Attempt to call MediaProjection#stop() even if it might have already been called.
-                // If projection has already been stopped, then nothing will happen. Else, stop
-                // will be logged as a manually requested stop from host app.
-                mMediaProjection.stop();
-            } else {
-                // In any other case, the stop reason is related to the recorder, so pass it on here
-                mMediaProjection.stop(stopReason);
-            }
-        });
+        closer.register(mMediaProjection::stop);
         closer.register(this::stopInternalAudioRecording);
 
         closer.close();
@@ -334,7 +323,7 @@
     @Override
     public void onStop() {
         Log.d(TAG, "The system notified about stopping the projection");
-        mListener.onStopped(StopReason.STOP_UNKNOWN);
+        mListener.onStopped();
     }
 
     private void stopInternalAudioRecording() {
@@ -464,7 +453,7 @@
          * For example, this might happen when doing partial screen sharing of an app
          * and the app that is being captured is closed.
          */
-        void onStopped(@StopReason int stopReason);
+        void onStopped();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepository.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepository.kt
index b6b8ffa..9eeb3b9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepository.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.screenrecord.data.repository
 
-import android.media.projection.StopReason
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.screenrecord.RecordingController
@@ -42,7 +41,7 @@
     val screenRecordState: Flow<ScreenRecordModel>
 
     /** Stops the recording. */
-    suspend fun stopRecording(@StopReason stopReason: Int)
+    suspend fun stopRecording()
 }
 
 @SysUISingleton
@@ -96,7 +95,7 @@
         }
     }
 
-    override suspend fun stopRecording(@StopReason stopReason: Int) {
-        withContext(bgCoroutineContext) { recordingController.stopRecording(stopReason) }
+    override suspend fun stopRecording() {
+        withContext(bgCoroutineContext) { recordingController.stopRecording() }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
index 229cef9..b3dbf29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor
 
-import android.media.projection.StopReason
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.LogBuffer
@@ -66,9 +65,7 @@
 
     /** Stops the currently active MediaRouter cast. */
     fun stopCasting() {
-        activeCastDevice.value?.let {
-            mediaRouterRepository.stopCasting(it, StopReason.STOP_PRIVACY_CHIP)
-        }
+        activeCastDevice.value?.let { mediaRouterRepository.stopCasting(it) }
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
index 0b5e669..f5952f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.chips.screenrecord.domain.interactor
 
-import android.media.projection.StopReason
 import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -141,7 +140,7 @@
 
     /** Stops the recording. */
     fun stopRecording() {
-        scope.launch { screenRecordRepository.stopRecording(StopReason.STOP_PRIVACY_CHIP) }
+        scope.launch { screenRecordRepository.stopRecording() }
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
index ece5a3f..a3dcc3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.policy;
 
-import android.media.projection.StopReason;
 import com.android.systemui.Dumpable;
 import com.android.systemui.statusbar.policy.CastController.Callback;
 
@@ -27,7 +26,7 @@
     void setCurrentUserId(int currentUserId);
     List<CastDevice> getCastDevices();
     void startCasting(CastDevice device);
-    void stopCasting(CastDevice device, @StopReason int stopReason);
+    void stopCasting(CastDevice device);
 
     /**
      * @return whether we have a connected device.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index ab20850..52f80fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -185,13 +185,13 @@
     }
 
     @Override
-    public void stopCasting(CastDevice device, @StopReason int stopReason) {
+    public void stopCasting(CastDevice device) {
         final boolean isProjection = device.getTag() instanceof MediaProjectionInfo;
         mLogger.logStopCasting(isProjection);
         if (isProjection) {
             final MediaProjectionInfo projection = (MediaProjectionInfo) device.getTag();
             if (Objects.equals(mProjectionManager.getActiveProjectionInfo(), projection)) {
-                mProjectionManager.stopActiveProjection(stopReason);
+                mProjectionManager.stopActiveProjection(StopReason.STOP_QS_TILE);
             } else {
                 mLogger.logStopCastingNoProjection(projection);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt
index 35ea0ea..64cda1f2 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt
@@ -41,7 +41,13 @@
     }
 
     override fun accept(event: MotionEvent) {
-        if (!isThreeFingerTouchpadSwipe(event)) return
+        if (!isMultifingerTouchpadSwipe(event)) return
+        if (!isThreeFingerTouchpadSwipe(event)) {
+            if (event.actionMasked == MotionEvent.ACTION_UP) {
+                gestureStateChangedCallback(GestureState.Error)
+            }
+            return
+        }
         val gestureState = distanceTracker.processEvent(event)
         updateGestureState(
             gestureStateChangedCallback,
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureRecognizer.kt
index 68a2ef9..884f08e 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureRecognizer.kt
@@ -35,6 +35,11 @@
         event.getAxisValue(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT) == fingerCount.toFloat()
 }
 
+fun isMultifingerTouchpadSwipe(event: MotionEvent): Boolean {
+    return event.classification == MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE ||
+        event.classification == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE
+}
+
 fun isTwoFingerSwipe(event: MotionEvent): Boolean {
     return event.classification == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE
 }
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt
index 9801626..7767a46 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt
@@ -40,7 +40,13 @@
     }
 
     override fun accept(event: MotionEvent) {
-        if (!isThreeFingerTouchpadSwipe(event)) return
+        if (!isMultifingerTouchpadSwipe(event)) return
+        if (!isThreeFingerTouchpadSwipe(event)) {
+            if (event.actionMasked == MotionEvent.ACTION_UP) {
+                gestureStateChangedCallback(GestureState.Error)
+            }
+            return
+        }
         val gestureState = distanceTracker.processEvent(event)
         velocityTracker.accept(event)
         updateGestureState(
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt
index 5ff583a..74746de 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt
@@ -43,7 +43,13 @@
     }
 
     override fun accept(event: MotionEvent) {
-        if (!isThreeFingerTouchpadSwipe(event)) return
+        if (!isMultifingerTouchpadSwipe(event)) return
+        if (!isThreeFingerTouchpadSwipe(event)) {
+            if (event.actionMasked == MotionEvent.ACTION_UP) {
+                gestureStateChangedCallback(GestureState.Error)
+            }
+            return
+        }
         val gestureState = distanceTracker.processEvent(event)
         velocityTracker.accept(event)
 
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
index 21e2917..dd275bd 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
@@ -40,9 +40,8 @@
         return if (isFromTouchpad && !buttonClick) {
             if (isTwoFingerSwipe(event)) {
                 easterEggGestureMonitor.processTouchpadEvent(event)
-            } else {
-                gestureRecognizer.accept(event)
             }
+            gestureRecognizer.accept(event)
             true
         } else {
             false
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index d8d6f2e9..330b887 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -4,6 +4,8 @@
 import android.os.Handler
 import android.os.Looper
 import android.os.UserManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
 import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf
 import android.testing.TestableLooper
@@ -20,10 +22,12 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.qs.TileDetailsViewModel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.flags.QSComposeFragment
+import com.android.systemui.qs.flags.QsDetailedView
 import com.android.systemui.qs.flags.QsInCompose.isEnabled
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
@@ -35,6 +39,7 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertTrue
 import kotlinx.coroutines.Job
 import org.junit.After
 import org.junit.Before
@@ -199,6 +204,7 @@
     }
 
     @Test
+    @DisableFlags(QsDetailedView.FLAG_NAME)
     fun handleClick_hasSatelliteFeatureButNoQsTileDialogAndClickIsProcessing_doNothing() {
         mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
         `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
@@ -212,6 +218,7 @@
     }
 
     @Test
+    @DisableFlags(QsDetailedView.FLAG_NAME)
     fun handleClick_noSatelliteFeatureAndNoQsTileDialog_directSetBtEnable() {
         mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
         `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
@@ -223,6 +230,7 @@
     }
 
     @Test
+    @DisableFlags(QsDetailedView.FLAG_NAME)
     fun handleClick_noSatelliteFeatureButHasQsTileDialog_showDialog() {
         mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
         `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
@@ -234,6 +242,35 @@
     }
 
     @Test
+    @EnableFlags(QsDetailedView.FLAG_NAME)
+    fun handleClick_hasSatelliteFeatureAndQsDetailedViewIsEnabledAndClickIsProcessing_doNothing() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
+            .thenReturn(false)
+        `when`(clickJob.isCompleted).thenReturn(false)
+        tile.mClickJob = clickJob
+        var currentModel: TileDetailsViewModel? = null
+
+        tile.getDetailsViewModel { model: TileDetailsViewModel? -> currentModel = model }
+
+        // Click is not allowed.
+        assertThat(currentModel).isEqualTo(null)
+    }
+
+    @Test
+    @EnableFlags(QsDetailedView.FLAG_NAME)
+    fun handleClick_noSatelliteFeatureAndQsDetailedViewIsEnabled_returnDetailsViewModel() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
+            .thenReturn(false)
+        var currentModel: TileDetailsViewModel? = null
+
+        tile.getDetailsViewModel { model: TileDetailsViewModel? -> currentModel = model }
+
+        assertTrue(currentModel != null)
+    }
+
+    @Test
     fun testMetadataListener_whenDisconnected_isUnregistered() {
         val state = QSTile.BooleanState()
         val cachedDevice = mock<CachedBluetoothDevice>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index a17f100..afff485 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -36,7 +36,6 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
-import android.media.projection.StopReason;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -155,7 +154,7 @@
         PendingIntent stopIntent = Mockito.mock(PendingIntent.class);
 
         mController.startCountdown(0, 0, startIntent, stopIntent);
-        mController.stopRecording(StopReason.STOP_UNKNOWN);
+        mController.stopRecording();
 
         assertFalse(mController.isStarting());
         assertFalse(mController.isRecording());
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
index 4aa132c..8c9163d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
@@ -43,5 +43,6 @@
             selectedUserInteractor,
             keyguardEnabledInteractor,
             keyguardServiceLockNowInteractor,
+            keyguardInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/FakeMediaRouterRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/FakeMediaRouterRepository.kt
index d5637cb..8aa7a03 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/FakeMediaRouterRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/FakeMediaRouterRepository.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.mediarouter.data.repository
 
-import android.media.projection.StopReason
 import com.android.systemui.statusbar.policy.CastDevice
 import kotlinx.coroutines.flow.MutableStateFlow
 
@@ -26,7 +25,7 @@
     var lastStoppedDevice: CastDevice? = null
         private set
 
-    override fun stopCasting(device: CastDevice, @StopReason stopReason: Int) {
+    override fun stopCasting(device: CastDevice) {
         lastStoppedDevice = device
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt
index 06822a6..4714969 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt
@@ -19,7 +19,6 @@
 import com.android.internal.logging.InstanceId
 import com.android.systemui.animation.Expandable
 import com.android.systemui.plugins.qs.QSTile
-import com.android.systemui.plugins.qs.TileDetailsViewModel
 
 class FakeQSTile(var user: Int, var available: Boolean = true) : QSTile {
     private var tileSpec: String? = null
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/screenrecord/data/repository/FakeScreenRecordRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/screenrecord/data/repository/FakeScreenRecordRepository.kt
index 4c9e174..30b4763 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/screenrecord/data/repository/FakeScreenRecordRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/screenrecord/data/repository/FakeScreenRecordRepository.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.screenrecord.data.repository
 
-import android.media.projection.StopReason
 import com.android.systemui.screenrecord.data.model.ScreenRecordModel
 import kotlinx.coroutines.flow.MutableStateFlow
 
@@ -26,7 +25,7 @@
 
     var stopRecordingInvoked = false
 
-    override suspend fun stopRecording(@StopReason stopReason: Int) {
+    override suspend fun stopRecording() {
         stopRecordingInvoked = true
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeCastController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeCastController.kt
index da6b2ae..2df0c7a5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeCastController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeCastController.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.policy
 
-import android.media.projection.StopReason
 import java.io.PrintWriter
 
 class FakeCastController : CastController {
@@ -46,7 +45,7 @@
 
     override fun startCasting(device: CastDevice?) {}
 
-    override fun stopCasting(device: CastDevice?, @StopReason stopReason: Int) {
+    override fun stopCasting(device: CastDevice?) {
         lastStoppedDevice = device
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckerCastController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckerCastController.java
index 857dc85..2249bc0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckerCastController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckerCastController.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.utils.leaks;
 
-import android.media.projection.StopReason;
 import android.testing.LeakCheck;
 
 import com.android.systemui.statusbar.policy.CastController;
@@ -52,7 +51,7 @@
     }
 
     @Override
-    public void stopCasting(CastDevice device, @StopReason int stopReason) {
+    public void stopCasting(CastDevice device) {
 
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index e1b6c9c..5133575 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -245,22 +245,45 @@
                     final boolean complete =
                             event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE
                                     && !event.isCancelled();
+
+                    // TODO(b/355499907): Receive and handle held key gestures, which can be used
+                    // for continuous scaling and panning. In addition, handle multiple pan gestures
+                    // at the same time (e.g. user may try to pan diagonally) reasonably, including
+                    // decreasing diagonal movement by sqrt(2) to make it appear the same speed
+                    // as non-diagonal movement.
+
+                    if (!complete) {
+                        return false;
+                    }
+
                     final int gestureType = event.getKeyGestureType();
                     final int displayId = isDisplayIdValid(event.getDisplayId())
                             ? event.getDisplayId() : Display.DEFAULT_DISPLAY;
 
                     switch (gestureType) {
                         case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN:
-                            if (complete) {
                                 mAms.getMagnificationController().scaleMagnificationByStep(
                                         displayId, MagnificationController.ZOOM_DIRECTION_IN);
-                            }
                             return true;
                         case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT:
-                            if (complete) {
                                 mAms.getMagnificationController().scaleMagnificationByStep(
                                         displayId, MagnificationController.ZOOM_DIRECTION_OUT);
-                            }
+                            return true;
+                        case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_LEFT:
+                            mAms.getMagnificationController().panMagnificationByStep(
+                                    displayId, MagnificationController.PAN_DIRECTION_LEFT);
+                            return true;
+                        case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_RIGHT:
+                            mAms.getMagnificationController().panMagnificationByStep(
+                                    displayId, MagnificationController.PAN_DIRECTION_RIGHT);
+                            return true;
+                        case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_UP:
+                            mAms.getMagnificationController().panMagnificationByStep(
+                                    displayId, MagnificationController.PAN_DIRECTION_UP);
+                            return true;
+                        case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_DOWN:
+                            mAms.getMagnificationController().panMagnificationByStep(
+                                    displayId, MagnificationController.PAN_DIRECTION_DOWN);
                             return true;
                     }
                     return false;
@@ -270,7 +293,11 @@
                 public boolean isKeyGestureSupported(int gestureType) {
                     return switch (gestureType) {
                         case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN,
-                             KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT -> true;
+                             KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT,
+                             KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_LEFT,
+                             KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_RIGHT,
+                             KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_UP,
+                             KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_DOWN -> true;
                         default -> false;
                     };
                 }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 058b2be..2e131b6 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -35,14 +35,19 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.hardware.display.DisplayManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.DisplayMetrics;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
+import android.util.SparseDoubleArray;
 import android.util.SparseIntArray;
 import android.util.SparseLongArray;
+import android.util.TypedValue;
+import android.view.Display;
 import android.view.accessibility.MagnificationAnimationCallback;
 
 import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
@@ -102,6 +107,7 @@
     /** Whether the platform supports window magnification feature. */
     private final boolean mSupportWindowMagnification;
     private final MagnificationScaleStepProvider mScaleStepProvider;
+    private final MagnificationPanStepProvider mPanStepProvider;
 
     private final Executor mBackgroundExecutor;
 
@@ -132,7 +138,7 @@
             .UiChangesForAccessibilityCallbacks> mAccessibilityCallbacksDelegateArray =
             new SparseArray<>();
 
-    // Direction magnifier scale can be altered.
+    // Direction magnification scale can be altered.
     public static final int ZOOM_DIRECTION_IN = 0;
     public static final int ZOOM_DIRECTION_OUT = 1;
 
@@ -140,6 +146,16 @@
     public @interface ZoomDirection {
     }
 
+    // Directions magnification center can be moved.
+    public static final int PAN_DIRECTION_LEFT = 0;
+    public static final int PAN_DIRECTION_RIGHT = 1;
+    public static final int PAN_DIRECTION_UP = 2;
+    public static final int PAN_DIRECTION_DOWN = 3;
+
+    @IntDef({PAN_DIRECTION_LEFT, PAN_DIRECTION_RIGHT, PAN_DIRECTION_UP, PAN_DIRECTION_DOWN})
+    public @interface PanDirection {
+    }
+
     /**
      * A callback to inform the magnification transition result on the given display.
      */
@@ -188,6 +204,87 @@
         }
     }
 
+    /**
+     * An interface to configure how much the magnification center should be affected when panning
+     * in steps.
+     */
+    public interface MagnificationPanStepProvider {
+        /**
+         * Calculate the next value based on the current scale.
+         *
+         * @param currentScale The current magnification scale value.
+         * @param displayId The displayId for the display being magnified.
+         * @return The next pan step value.
+         */
+        float nextPanStep(float currentScale, int displayId);
+    }
+
+    public static class DefaultMagnificationPanStepProvider implements
+            MagnificationPanStepProvider, DisplayManager.DisplayListener {
+        // We want panning to be 40 dip per keystroke at scale 2, and 1 dip per keystroke at scale
+        // 20. This can be defined using y = mx + b to get the slope and intercept.
+        // This works even if the device does not allow magnification up to 20x; we will still get
+        // a reasonable lineary ramp of panning movement for each scale step.
+        private static final float DEFAULT_SCALE = 2.0f;
+        private static final float PAN_STEP_AT_DEFAULT_SCALE_DIP = 40.0f;
+        private static final float SCALE_FOR_1_DIP_PAN = 20.0f;
+
+        private SparseDoubleArray mPanStepSlopes;
+        private SparseDoubleArray mPanStepIntercepts;
+
+        private final DisplayManager mDisplayManager;
+
+        DefaultMagnificationPanStepProvider(Context context) {
+            mDisplayManager = context.getSystemService(DisplayManager.class);
+            mDisplayManager.registerDisplayListener(this, /*handler=*/null);
+            mPanStepSlopes = new SparseDoubleArray();
+            mPanStepIntercepts = new SparseDoubleArray();
+        }
+
+        @Override
+        public void onDisplayAdded(int displayId) {
+            updateForDisplay(displayId);
+        }
+
+        @Override
+        public void onDisplayChanged(int displayId) {
+            updateForDisplay(displayId);
+        }
+
+        @Override
+        public void onDisplayRemoved(int displayId) {
+            mPanStepSlopes.delete(displayId);
+            mPanStepIntercepts.delete(displayId);
+        }
+
+        @Override
+        public float nextPanStep(float currentScale, int displayId) {
+            if (mPanStepSlopes.indexOfKey(displayId) < 0) {
+                updateForDisplay(displayId);
+            }
+            return Math.max((float) (mPanStepSlopes.get(displayId) * currentScale
+                    + mPanStepIntercepts.get(displayId)), 1);
+        }
+
+        private void updateForDisplay(int displayId) {
+            Display display = mDisplayManager.getDisplay(displayId);
+            if (display == null) {
+                return;
+            }
+            DisplayMetrics metrics = new DisplayMetrics();
+            display.getMetrics(metrics);
+            final float panStepAtDefaultScaleInPx = TypedValue.convertDimensionToPixels(
+                    TypedValue.COMPLEX_UNIT_DIP, PAN_STEP_AT_DEFAULT_SCALE_DIP, metrics);
+            final float panStepAtMaxScaleInPx = TypedValue.convertDimensionToPixels(
+                    TypedValue.COMPLEX_UNIT_DIP, 1.0f, metrics);
+            final float panStepSlope = (panStepAtMaxScaleInPx - panStepAtDefaultScaleInPx)
+                    / (SCALE_FOR_1_DIP_PAN - DEFAULT_SCALE);
+            mPanStepSlopes.put(displayId, panStepSlope);
+            mPanStepIntercepts.put(displayId,
+                    panStepAtDefaultScaleInPx - panStepSlope * DEFAULT_SCALE);
+        }
+    }
+
     public MagnificationController(AccessibilityManagerService ams, Object lock,
             Context context, MagnificationScaleProvider scaleProvider,
             Executor backgroundExecutor) {
@@ -201,6 +298,7 @@
         mSupportWindowMagnification = context.getPackageManager().hasSystemFeature(
                 FEATURE_WINDOW_MAGNIFICATION);
         mScaleStepProvider = new DefaultMagnificationScaleStepProvider();
+        mPanStepProvider = new DefaultMagnificationPanStepProvider(mContext);
 
         mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag(context);
         mAlwaysOnMagnificationFeatureFlag.addOnChangedListener(
@@ -935,13 +1033,12 @@
     }
 
     /**
-     * Scales the magnifier on the given display one step in/out based on the zoomIn param.
+     * Scales the magnifier on the given display one step in/out based on the direction param.
      *
      * @param displayId The logical display id.
      * @param direction Whether the scale should be zoomed in or out.
-     * @return {@code true} if the magnification scale was affected.
      */
-    public boolean scaleMagnificationByStep(int displayId, @ZoomDirection int direction) {
+    public void scaleMagnificationByStep(int displayId, @ZoomDirection int direction) {
         if (getFullScreenMagnificationController().isActivated(displayId)) {
             final float magnificationScale = getFullScreenMagnificationController().getScale(
                     displayId);
@@ -950,7 +1047,6 @@
             getFullScreenMagnificationController().setScaleAndCenter(displayId,
                     nextMagnificationScale,
                     Float.NaN, Float.NaN, true, MAGNIFICATION_GESTURE_HANDLER_ID);
-            return nextMagnificationScale != magnificationScale;
         }
 
         if (getMagnificationConnectionManager().isWindowMagnifierEnabled(displayId)) {
@@ -959,10 +1055,51 @@
             final float nextMagnificationScale = mScaleStepProvider.nextScaleStep(
                     magnificationScale, direction);
             getMagnificationConnectionManager().setScale(displayId, nextMagnificationScale);
-            return nextMagnificationScale != magnificationScale;
+        }
+    }
+
+    /**
+     * Pans the magnifier on the given display one step left/right/up/down based on the direction
+     * param.
+     *
+     * @param displayId The logical display id.
+     * @param direction Whether the direction should be left/right/up/down.
+     */
+    public void panMagnificationByStep(int displayId, @PanDirection int direction) {
+        final boolean fullscreenActivated =
+                getFullScreenMagnificationController().isActivated(displayId);
+        final boolean windowActivated =
+                getMagnificationConnectionManager().isWindowMagnifierEnabled(displayId);
+        if (!fullscreenActivated && !windowActivated) {
+            return;
         }
 
-        return false;
+        final float scale = fullscreenActivated
+                ? getFullScreenMagnificationController().getScale(displayId)
+                        : getMagnificationConnectionManager().getScale(displayId);
+        final float step = mPanStepProvider.nextPanStep(scale, displayId);
+
+        float offsetX = 0;
+        float offsetY = 0;
+        if (direction == PAN_DIRECTION_LEFT) {
+            offsetX = -step;
+        } else if (direction == PAN_DIRECTION_RIGHT) {
+            offsetX = step;
+        } else if (direction == PAN_DIRECTION_UP) {
+            offsetY = -step;
+        } else if (direction == PAN_DIRECTION_DOWN) {
+            offsetY = step;
+        }
+
+        if (fullscreenActivated) {
+            final float centerX = getFullScreenMagnificationController().getCenterX(displayId);
+            final float centerY = getFullScreenMagnificationController().getCenterY(displayId);
+            getFullScreenMagnificationController().setScaleAndCenter(displayId, scale,
+                    centerX + offsetX, centerY + offsetY, true, MAGNIFICATION_GESTURE_HANDLER_ID);
+        } else {
+            getMagnificationConnectionManager().moveWindowMagnification(displayId, offsetX,
+                    offsetY);
+        }
     }
 
     private final class DisableMagnificationCallback implements
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index 65c446e..ec6c3b7 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -74,6 +74,16 @@
 }
 
 flag {
+  name: "multiple_fill_history"
+  namespace: "autofill"
+  description: "Allows tracking per Session FillEventHistory. As a bugfix flag to guard against DeviceConfig flag"
+  bug: "365630157"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "add_session_id_to_client_state"
   namespace: "autofill"
   description: "Include the session id into the FillEventHistory events as part of ClientState"
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 0fa43ac..11710c9 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -152,6 +152,15 @@
     @GuardedBy("mLock")
     private final SparseArray<Session> mSessions = new SparseArray<>();
 
+    /**
+     * Cache of FillEventHistory for active Sessions.
+     *
+     * <p>New histories are added whenever a Session is created and are kept until Sessions are
+     * removed through removeSessionLocked()
+     */
+    @GuardedBy("mLock")
+    private final SparseArray<FillEventHistory> mFillHistories = new SparseArray<>();
+
     /** The last selection */
     @GuardedBy("mLock")
     private FillEventHistory mEventHistory;
@@ -663,6 +672,10 @@
                 flags, mInputMethodManagerInternal, isPrimaryCredential);
         mSessions.put(newSession.id, newSession);
 
+        if (Flags.multipleFillHistory() && !forAugmentedAutofillOnly) {
+            mFillHistories.put(newSession.id, new FillEventHistory(sessionId, null));
+        }
+
         return newSession;
     }
 
@@ -756,6 +769,28 @@
                         TAG,
                         "removeSessionLocked(): removed " + sessionId);
             }
+
+            FillEventHistory history = null;
+
+            if (Flags.multipleFillHistory() && mFillHistories != null) {
+                history = mFillHistories.get(sessionId);
+                mFillHistories.delete(sessionId);
+            }
+
+            if (mInfo == null || mInfo.getServiceInfo() == null) {
+                if (sVerbose) {
+                    Slog.v(TAG, "removeSessionLocked(): early return because mInfo is null");
+                }
+                return;
+            }
+
+            if (mMaster == null) {
+                if (sVerbose) {
+                    Slog.v(TAG, "removeSessionLocked(): early return because mMaster is null");
+                }
+                return;
+            }
+
             RemoteFillService remoteService =
                     new RemoteFillService(
                             getContext(),
@@ -764,7 +799,8 @@
                             /* callbacks= */ null,
                             mMaster.isInstantServiceAllowed(),
                             /* credentialAutofillService= */ null);
-            remoteService.onSessionDestroyed(null);
+
+            remoteService.onSessionDestroyed(history);
         }
     }
 
@@ -886,6 +922,10 @@
             }
         }
         mSessions.clear();
+        if (Flags.multipleFillHistory()) {
+            mFillHistories.clear();
+        }
+
         for (int i = 0; i < remoteFillServices.size(); i++) {
             remoteFillServices.valueAt(i).destroy();
         }
@@ -944,60 +984,132 @@
         return true;
     }
 
-    /**
-     * Updates the last fill selection when an authentication was selected.
-     */
-    void setAuthenticationSelected(int sessionId, @Nullable Bundle clientState,
-            int uiType, @Nullable AutofillId focusedId) {
-        synchronized (mLock) {
-            if (isValidEventLocked("setAuthenticationSelected()", sessionId)) {
-                mEventHistory.addEvent(
-                        new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState, null, null,
-                                null, null, null, null, null, null,
-                                NO_SAVE_UI_REASON_NONE, uiType, focusedId));
+    @GuardedBy("mLock")
+    void addEventToHistory(String eventName, int sessionId, Event event) {
+        // For the singleton filleventhistory
+        if (isValidEventLocked(eventName, sessionId)) {
+            mEventHistory.addEvent(event);
+        }
+
+        if (Flags.multipleFillHistory()) {
+            FillEventHistory history = mFillHistories.get(sessionId);
+            if (history != null) {
+                history.addEvent(event);
+            } else {
+                if (sVerbose) {
+                    Slog.v(TAG, eventName
+                            + " not logged because FillEventHistory is not tracked for: "
+                            + sessionId);
+                }
             }
         }
     }
 
     /**
-     * Updates the last fill selection when an dataset authentication was selected.
+     * Updates the last fill selection when an authentication was selected.
      */
-    void logDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId,
-            @Nullable Bundle clientState, int uiType, @Nullable AutofillId focusedId) {
+    void setAuthenticationSelected(int sessionId, @Nullable Bundle clientState,
+            int uiType, @Nullable AutofillId focusedId, boolean shouldAdd) {
         synchronized (mLock) {
-            if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) {
-                mEventHistory.addEvent(
-                        new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset,
-                                clientState, null, null, null, null, null, null, null, null,
-                                NO_SAVE_UI_REASON_NONE, uiType, focusedId));
+
+            String methodName = "setAuthenticationSelected()";
+
+            if (!shouldAdd) {
+                if (sVerbose) {
+                    Slog.v(TAG, methodName + " not logged because shouldAdd is false");
+                }
+                return;
             }
+
+            Event event =
+                    new Event(
+                            Event.TYPE_AUTHENTICATION_SELECTED,
+                            null,
+                            clientState,
+                            null,
+                            null,
+                            null,
+                            null,
+                            null,
+                            null,
+                            null,
+                            null,
+                            NO_SAVE_UI_REASON_NONE,
+                            uiType,
+                            focusedId);
+
+            addEventToHistory(methodName, sessionId, event);
+        }
+    }
+
+    /** Updates the last fill selection when a dataset authentication was selected. */
+    void logDatasetAuthenticationSelected(
+            @Nullable String selectedDataset,
+            int sessionId,
+            @Nullable Bundle clientState,
+            int uiType,
+            @Nullable AutofillId focusedId,
+            boolean shouldAdd) {
+        synchronized (mLock) {
+            String methodName = "logDatasetAuthenticationSelected()";
+
+            if (!shouldAdd) {
+                if (sVerbose) {
+                    Slog.v(TAG, methodName + " not logged because shouldAdd is false");
+                }
+                return;
+            }
+
+            Event event = new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset,
+                                clientState, null, null, null, null, null, null, null, null,
+                                NO_SAVE_UI_REASON_NONE, uiType, focusedId);
+            addEventToHistory(methodName, sessionId, event);
         }
     }
 
     /**
      * Updates the last fill selection when an save Ui is shown.
      */
-    void logSaveShown(int sessionId, @Nullable Bundle clientState) {
+    void logSaveShown(int sessionId, @Nullable Bundle clientState, boolean shouldAdd) {
         synchronized (mLock) {
-            if (isValidEventLocked("logSaveShown()", sessionId)) {
-                mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState, null,
-                        null, null, null, null, null, null, null, /* focusedId= */ null));
+            String methodName = "logSaveShown()";
+
+            if (!shouldAdd) {
+                if (sVerbose) {
+                    Slog.v(TAG, methodName + " not logged because shouldAdd is false");
+                }
+                return;
             }
+
+            Event event = new Event(Event.TYPE_SAVE_SHOWN, null, clientState, null,
+                        null, null, null, null, null, null, null, /* focusedId= */ null);
+
+            addEventToHistory(methodName, sessionId, event);
         }
     }
 
-    /**
-     * Updates the last fill response when a dataset was selected.
-     */
-    void logDatasetSelected(@Nullable String selectedDataset, int sessionId,
-            @Nullable Bundle clientState,  int uiType, @Nullable AutofillId focusedId) {
+    /** Updates the last fill response when a dataset was selected. */
+    void logDatasetSelected(
+            @Nullable String selectedDataset,
+            int sessionId,
+            @Nullable Bundle clientState,
+            int uiType,
+            @Nullable AutofillId focusedId,
+            boolean shouldAdd) {
         synchronized (mLock) {
-            if (isValidEventLocked("logDatasetSelected()", sessionId)) {
-                mEventHistory.addEvent(
-                        new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null,
-                                null, null, null, null, null, null, null, NO_SAVE_UI_REASON_NONE,
-                                uiType, focusedId));
+            String methodName = "logDatasetSelected()";
+
+            if (!shouldAdd) {
+                if (sVerbose) {
+                    Slog.v(TAG, methodName + " not logged because shouldAdd is false");
+                }
+                return;
             }
+
+            Event event = new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null,
+                                null, null, null, null, null, null, null, NO_SAVE_UI_REASON_NONE,
+                                uiType, focusedId);
+            addEventToHistory(methodName, sessionId, event);
         }
     }
 
@@ -1005,40 +1117,75 @@
      * Updates the last fill response when a dataset is shown.
      */
     void logDatasetShown(int sessionId, @Nullable Bundle clientState, int uiType,
-            @Nullable AutofillId focusedId) {
+            @Nullable AutofillId focusedId, boolean shouldAdd) {
         synchronized (mLock) {
-            if (isValidEventLocked("logDatasetShown", sessionId)) {
-                mEventHistory.addEvent(
-                        new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null,
+            String methodName = "logDatasetShown()";
+
+            if (!shouldAdd) {
+                if (sVerbose) {
+                    Slog.v(TAG, methodName + " not logged because shouldAdd is false");
+                }
+                return;
+            }
+
+            Event event = new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null,
                                 null, null, null, null, null, NO_SAVE_UI_REASON_NONE,
-                                uiType, focusedId));
+                                uiType, focusedId);
+            addEventToHistory(methodName, sessionId, event);
+        }
+    }
+
+    void logViewEnteredForHistory(
+            int sessionId,
+            @Nullable Bundle clientState,
+            FillEventHistory history,
+            @Nullable AutofillId focusedId) {
+        if (history.getEvents() != null) {
+            // Do not log this event more than once
+            for (Event event : history.getEvents()) {
+                if (event.getType() == Event.TYPE_VIEW_REQUESTED_AUTOFILL) {
+                    if (sVerbose) {
+                        Slog.v(TAG, "logViewEntered: already logged TYPE_VIEW_REQUESTED_AUTOFILL");
+                    }
+                    return;
+                }
             }
         }
+
+        history.addEvent(
+                new Event(Event.TYPE_VIEW_REQUESTED_AUTOFILL, null, clientState, null,
+                        null, null, null, null, null, null, null, focusedId));
     }
 
     /**
      * Updates the last fill response when a view was entered.
      */
     void logViewEntered(int sessionId, @Nullable Bundle clientState,
-            @Nullable AutofillId focusedId) {
+            @Nullable AutofillId focusedId, boolean shouldAdd) {
         synchronized (mLock) {
-            if (!isValidEventLocked("logViewEntered", sessionId)) {
+            String methodName = "logViewEntered()";
+
+            if (!shouldAdd) {
+                if (sVerbose) {
+                    Slog.v(TAG, methodName + " not logged because shouldAdd is false");
+                }
                 return;
             }
 
-            if (mEventHistory.getEvents() != null) {
-                // Do not log this event more than once
-                for (Event event : mEventHistory.getEvents()) {
-                    if (event.getType() == Event.TYPE_VIEW_REQUESTED_AUTOFILL) {
-                        Slog.v(TAG, "logViewEntered: already logged TYPE_VIEW_REQUESTED_AUTOFILL");
-                        return;
-                    }
-                }
+            // This log does not call addEventToHistory() because each distinct FillEventHistory
+            // can only contain 1 TYPE_VIEW_REQUESTED_AUTOFILL event. Therefore, checking both
+            // the singleton FillEventHistory and the per Session FillEventHistory is necessary
+
+            if (isValidEventLocked(methodName, sessionId)) {
+                logViewEnteredForHistory(sessionId, clientState, mEventHistory, focusedId);
             }
 
-            mEventHistory.addEvent(
-                    new Event(Event.TYPE_VIEW_REQUESTED_AUTOFILL, null, clientState, null,
-                            null, null, null, null, null, null, null, focusedId));
+            if (Flags.multipleFillHistory()) {
+                FillEventHistory history = mFillHistories.get(sessionId);
+                if (history != null) {
+                    logViewEnteredForHistory(sessionId, clientState, history, focusedId);
+                }
+            }
         }
     }
 
@@ -1096,12 +1243,12 @@
             @Nullable ArrayList<String> changedDatasetIds,
             @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
             @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
-            @NonNull ComponentName appComponentName, boolean compatMode) {
+            @NonNull ComponentName appComponentName, boolean compatMode, boolean shouldAdd) {
         logContextCommittedLocked(sessionId, clientState, selectedDatasets, ignoredDatasets,
                 changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
                 manuallyFilledDatasetIds, /* detectedFieldIdsList= */ null,
                 /* detectedFieldClassificationsList= */ null, appComponentName, compatMode,
-                Event.NO_SAVE_UI_REASON_NONE);
+                Event.NO_SAVE_UI_REASON_NONE, shouldAdd);
     }
 
     @GuardedBy("mLock")
@@ -1115,9 +1262,19 @@
             @Nullable ArrayList<AutofillId> detectedFieldIdsList,
             @Nullable ArrayList<FieldClassification> detectedFieldClassificationsList,
             @NonNull ComponentName appComponentName, boolean compatMode,
-            @NoSaveReason int saveDialogNotShowReason) {
-        if (isValidEventLocked("logDatasetNotSelected()", sessionId)) {
+            @NoSaveReason int saveDialogNotShowReason,
+            boolean shouldAdd) {
+
+        String methodName = "logContextCommittedLocked()";
+
+        if (!shouldAdd) {
             if (sVerbose) {
+                Slog.v(TAG, methodName + " not logged because shouldAdd is false");
+            }
+            return;
+        }
+
+        if (sVerbose) {
                 Slog.v(TAG, "logContextCommitted() with FieldClassification: id=" + sessionId
                         + ", selectedDatasets=" + selectedDatasets
                         + ", ignoredDatasetIds=" + ignoredDatasets
@@ -1129,44 +1286,58 @@
                         + ", appComponentName=" + appComponentName.toShortString()
                         + ", compatMode=" + compatMode
                         + ", saveDialogNotShowReason=" + saveDialogNotShowReason);
-            }
-            AutofillId[] detectedFieldsIds = null;
-            FieldClassification[] detectedFieldClassifications = null;
-            if (detectedFieldIdsList != null) {
-                detectedFieldsIds = new AutofillId[detectedFieldIdsList.size()];
-                detectedFieldIdsList.toArray(detectedFieldsIds);
-                detectedFieldClassifications =
-                        new FieldClassification[detectedFieldClassificationsList.size()];
-                detectedFieldClassificationsList.toArray(detectedFieldClassifications);
-
-                final int numberFields = detectedFieldsIds.length;
-                int totalSize = 0;
-                float totalScore = 0;
-                for (int i = 0; i < numberFields; i++) {
-                    final FieldClassification fc = detectedFieldClassifications[i];
-                    final List<Match> matches = fc.getMatches();
-                    final int size = matches.size();
-                    totalSize += size;
-                    for (int j = 0; j < size; j++) {
-                        totalScore += matches.get(j).getScore();
-                    }
-                }
-
-                final int averageScore = (int) ((totalScore * 100) / totalSize);
-                mMetricsLogger.write(Helper
-                        .newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES,
-                                appComponentName, getServicePackageName(), sessionId, compatMode)
-                        .setCounterValue(numberFields)
-                        .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE,
-                                averageScore));
-            }
-            mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null,
-                    clientState, selectedDatasets, ignoredDatasets,
-                    changedFieldIds, changedDatasetIds,
-                    manuallyFilledFieldIds, manuallyFilledDatasetIds,
-                    detectedFieldsIds, detectedFieldClassifications, saveDialogNotShowReason,
-                    /* focusedId= */ null));
         }
+
+        AutofillId[] detectedFieldsIds = null;
+        FieldClassification[] detectedFieldClassifications = null;
+        if (detectedFieldIdsList != null) {
+            detectedFieldsIds = new AutofillId[detectedFieldIdsList.size()];
+            detectedFieldIdsList.toArray(detectedFieldsIds);
+            detectedFieldClassifications =
+                    new FieldClassification[detectedFieldClassificationsList.size()];
+            detectedFieldClassificationsList.toArray(detectedFieldClassifications);
+
+            final int numberFields = detectedFieldsIds.length;
+            int totalSize = 0;
+            float totalScore = 0;
+            for (int i = 0; i < numberFields; i++) {
+                final FieldClassification fc = detectedFieldClassifications[i];
+                final List<Match> matches = fc.getMatches();
+                final int size = matches.size();
+                totalSize += size;
+                for (int j = 0; j < size; j++) {
+                    totalScore += matches.get(j).getScore();
+                }
+            }
+
+            final int averageScore = (int) ((totalScore * 100) / totalSize);
+            mMetricsLogger.write(
+                    Helper.newLogMaker(
+                                    MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES,
+                                    appComponentName,
+                                    getServicePackageName(),
+                                    sessionId,
+                                    compatMode)
+                            .setCounterValue(numberFields)
+                            .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE, averageScore));
+        }
+        Event event =
+                new Event(
+                        Event.TYPE_CONTEXT_COMMITTED,
+                        null,
+                        clientState,
+                        selectedDatasets,
+                        ignoredDatasets,
+                        changedFieldIds,
+                        changedDatasetIds,
+                        manuallyFilledFieldIds,
+                        manuallyFilledDatasetIds,
+                        detectedFieldsIds,
+                        detectedFieldClassifications,
+                        saveDialogNotShowReason,
+                        /* focusedId= */ null);
+
+        addEventToHistory(methodName, sessionId, event);
     }
 
     /**
@@ -1174,7 +1345,9 @@
      *
      * @param callingUid The calling uid
      * @return The history for the autofill or the augmented autofill events depending on the {@code
-     * callingUid}, or {@code null} if there is none.
+     *     callingUid}, or {@code null} if there is none.
+     * @deprecated Use {@link
+     *     android.service.autofill.AutofillService#onSessionDestroyed(FillEventHistory)}
      */
     FillEventHistory getFillEventHistory(int callingUid) {
         synchronized (mLock) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index ba9865d..3ecff3b 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1962,7 +1962,7 @@
 
             if (mLogViewEntered) {
                 mLogViewEntered = false;
-                mService.logViewEntered(id, null, mCurrentViewId);
+                mService.logViewEntered(id, null, mCurrentViewId, shouldAddEventToHistory());
             }
         }
 
@@ -2866,7 +2866,12 @@
                 forceRemoveFromServiceLocked();
                 return;
             }
-            mService.setAuthenticationSelected(id, mClientState, uiType, mCurrentViewId);
+            mService.setAuthenticationSelected(
+                    id,
+                    mClientState,
+                    uiType,
+                    mCurrentViewId,
+                    shouldAddEventToHistory());
         }
 
 
@@ -2941,7 +2946,12 @@
                 if (!mLoggedInlineDatasetShown) {
                     // Chip inflation already logged, do not log again.
                     // This is needed because every chip inflation will call this.
-                    mService.logDatasetShown(this.id, mClientState, uiType, mCurrentViewId);
+                    mService.logDatasetShown(
+                            this.id,
+                            mClientState,
+                            uiType,
+                            mCurrentViewId,
+                            shouldAddEventToHistory());
                     Slog.d(TAG, "onShown(): " + uiType + ", " + numDatasetsShown);
                 }
                 mLoggedInlineDatasetShown = true;
@@ -2949,7 +2959,12 @@
                 mPresentationStatsEventLogger.logWhenDatasetShown(numDatasetsShown);
                 // Explicitly sets maybeSetSuggestionPresentedTimestampMs
                 mPresentationStatsEventLogger.maybeSetSuggestionPresentedTimestampMs();
-                mService.logDatasetShown(this.id, mClientState, uiType, mCurrentViewId);
+                mService.logDatasetShown(
+                        this.id,
+                        mClientState,
+                        uiType,
+                        mCurrentViewId,
+                        shouldAddEventToHistory());
                 Slog.d(TAG, "onShown(): " + uiType + ", " + numDatasetsShown);
             }
         }
@@ -3943,7 +3958,8 @@
                 detectedFieldClassifications,
                 mComponentName,
                 mCompatMode,
-                saveDialogNotShowReason);
+                saveDialogNotShowReason,
+                shouldAddEventToHistory());
         mSessionCommittedEventLogger.maybeSetCommitReason(commitReason);
         mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount);
         mSaveEventLogger.maybeSetSaveUiNotShownReason(saveDialogNotShowReason);
@@ -4590,7 +4606,7 @@
     }
 
     private void logSaveShown() {
-        mService.logSaveShown(id, mClientState);
+        mService.logSaveShown(id, mClientState, shouldAddEventToHistory());
     }
 
     @Nullable
@@ -5248,7 +5264,8 @@
                         // so this calling logViewEntered will be a nop.
                         // Calling logViewEntered() twice will only log it once
                         // TODO(271181979): this is broken for multiple partitions
-                        mService.logViewEntered(this.id, null, mCurrentViewId);
+                        mService.logViewEntered(
+                                this.id, null, mCurrentViewId, shouldAddEventToHistory());
                     }
 
                     // If this is the first time view is entered for inline, the last
@@ -6863,8 +6880,13 @@
             // Autofill it directly...
             if (dataset.getAuthentication() == null) {
                 if (generateEvent) {
-                    mService.logDatasetSelected(dataset.getId(), id, mClientState, uiType,
-                            mCurrentViewId);
+                    mService.logDatasetSelected(
+                            dataset.getId(),
+                            id,
+                            mClientState,
+                            uiType,
+                            mCurrentViewId,
+                            shouldAddEventToHistory());
                 }
                 if (mCurrentViewId != null) {
                     mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
@@ -6875,7 +6897,7 @@
 
             // ...or handle authentication.
             mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState, uiType,
-                        mCurrentViewId);
+                        mCurrentViewId, shouldAddEventToHistory());
             mPresentationStatsEventLogger.maybeSetAuthenticationType(
                     AUTHENTICATION_TYPE_DATASET_AUTHENTICATION);
             // does not matter the value of isPrimary because null response won't be overridden.
@@ -8018,6 +8040,32 @@
         mService.getMaster().logRequestLocked(historyItem);
     }
 
+    /**
+     * Don't add secondary providers to FillEventHistory
+     */
+    boolean shouldAddEventToHistory() {
+
+        FillResponse lastResponse = null;
+
+        synchronized (mLock) {
+            lastResponse = getLastResponseLocked("shouldAddEventToHistory(%s)");
+        }
+
+        // There might be events (like TYPE_VIEW_REQUESTED_AUTOFILL) that are
+        // generated before FillRequest/FillResponse mechanism are started, so
+        // still need to log it
+        if (lastResponse == null) {
+            return true;
+        }
+
+        if (mRequestId.isSecondaryProvider(lastResponse.getRequestId())) {
+            // The request was to a secondary provider - don't log these events
+            return false;
+        }
+
+        return true;
+    }
+
     private void wtf(@Nullable Exception e, String fmt, Object... args) {
         final String message = String.format(fmt, args);
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index 73d5630..8681ea5 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -221,6 +221,18 @@
             systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_EQUALS,
                     KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
                     KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN));
+            systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_DPAD_LEFT,
+                    KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON,
+                    KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_LEFT));
+            systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_DPAD_RIGHT,
+                    KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON,
+                    KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_RIGHT));
+            systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_DPAD_UP,
+                    KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON,
+                    KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_UP));
+            systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_DPAD_DOWN,
+                    KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON,
+                    KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_DOWN));
             systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_M,
                     KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
                     KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION));
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 6800142..c653dec 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2723,17 +2723,7 @@
             return false;
         }
 
-        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
-        if (Flags.imeSwitcherRevamp()) {
-            // The IME switcher button should be shown when the current IME specified a
-            // language settings activity.
-            final var curImi = settings.getMethodMap().get(settings.getSelectedInputMethod());
-            if (curImi != null && curImi.createImeLanguageSettingsActivityIntent() != null) {
-                return true;
-            }
-        }
-
-        return hasMultipleSubtypesForSwitcher(false /* nonAuxOnly */, settings);
+        return hasMultipleSubtypesForSwitcher(false /* nonAuxOnly */, userId);
     }
 
     /**
@@ -2741,10 +2731,11 @@
      * across all enabled IMEs for the given user.
      *
      * @param nonAuxOnly whether to check only for non auxiliary subtypes.
-     * @param settings   the input method settings under the given user ID.
+     * @param userId     the id of the user for which to check the number of subtypes.
      */
     private static boolean hasMultipleSubtypesForSwitcher(boolean nonAuxOnly,
-            @NonNull InputMethodSettings settings) {
+            @UserIdInt int userId) {
+        final var settings = InputMethodSettingsRepository.get(userId);
         List<InputMethodInfo> imes = settings.getEnabledInputMethodListWithFilter(
                 InputMethodInfo::shouldShowInInputMethodPicker);
         final int numImes = imes.size();
@@ -4130,8 +4121,7 @@
     @GuardedBy("ImfLock.class")
     private void onImeSwitchButtonClickLocked(int displayId, @NonNull UserData userData) {
         final int userId = userData.mUserId;
-        final var settings = InputMethodSettingsRepository.get(userId);
-        if (hasMultipleSubtypesForSwitcher(true /* nonAuxOnly */, settings)) {
+        if (hasMultipleSubtypesForSwitcher(true /* nonAuxOnly */, userId)) {
             switchToNextInputMethodLocked(false /* onlyCurrentIme */, userData);
         } else {
             showInputMethodPickerFromSystem(
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index 556cc03..d29fde2 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -16,7 +16,6 @@
 
 package com.android.server.location.contexthub;
 
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.hardware.location.ContextHubManager.AUTHORIZATION_DENIED;
 import static android.hardware.location.ContextHubManager.AUTHORIZATION_DENIED_GRACE_PERIOD;
 import static android.hardware.location.ContextHubManager.AUTHORIZATION_GRANTED;
@@ -25,7 +24,6 @@
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
-import android.chre.flags.Flags;
 import android.compat.Compatibility;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
@@ -655,7 +653,13 @@
         // If in the grace period, don't check permissions state since it'll cause cleanup
         // messages to be dropped.
         if (authState == AUTHORIZATION_DENIED
-                || !notePermissions(messagePermissions, RECEIVE_MSG_NOTE + nanoAppId)) {
+                || !ContextHubServiceUtil.notePermissions(
+                        mAppOpsManager,
+                        mUid,
+                        mPackage,
+                        mAttributionTag,
+                        messagePermissions,
+                        RECEIVE_MSG_NOTE + nanoAppId)) {
             Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage
                     + " doesn't have permission");
             return ErrorCode.PERMISSION_DENIED;
@@ -754,56 +758,6 @@
     }
 
     /**
-     * Checks that this client has all of the provided permissions.
-     *
-     * @param permissions list of permissions to check
-     * @return true if the client has all of the permissions granted
-     */
-    boolean hasPermissions(List<String> permissions) {
-        for (String permission : permissions) {
-            if (mContext.checkPermission(permission, mPid, mUid) != PERMISSION_GRANTED) {
-                Log.e(TAG, "no permission for " + permission);
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Attributes the provided permissions to the package of this client.
-     *
-     * @param permissions list of permissions covering data the client is about to receive
-     * @param noteMessage message that should be noted alongside permissions attribution to
-     *     facilitate debugging
-     * @return true if client has ability to use all of the provided permissions
-     */
-    boolean notePermissions(List<String> permissions, String noteMessage) {
-        for (String permission : permissions) {
-            int opCode = AppOpsManager.permissionToOpCode(permission);
-            if (opCode != AppOpsManager.OP_NONE) {
-                try {
-                    if (mAppOpsManager.noteOp(opCode, mUid, mPackage, mAttributionTag, noteMessage)
-                            != AppOpsManager.MODE_ALLOWED) {
-                        return false;
-                    }
-                } catch (SecurityException e) {
-                    Log.e(
-                            TAG,
-                            "SecurityException: noteOp for pkg "
-                                    + mPackage
-                                    + " opcode "
-                                    + opCode
-                                    + ": "
-                                    + e.getMessage());
-                    return false;
-                }
-            }
-        }
-
-        return true;
-    }
-
-    /**
      * @return true if the client is a PendingIntent client that has been cancelled.
      */
     boolean isPendingIntentCancelled() {
@@ -868,7 +822,8 @@
         synchronized (mMessageChannelNanoappIdMap) {
             // Check permission granted state synchronously since this method can be invoked from
             // multiple threads.
-            boolean hasPermissions = hasPermissions(nanoappPermissions);
+            boolean hasPermissions =
+                    ContextHubServiceUtil.hasPermissions(mContext, mPid, mUid, nanoappPermissions);
 
             curAuthState = mMessageChannelNanoappIdMap.getOrDefault(
                     nanoAppId, AUTHORIZATION_UNKNOWN);
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
index 4e1df76..2c072d0 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -145,7 +145,8 @@
         super.closeSession_enforcePermission();
         if (!mIsRegistered.get()) throw new IllegalStateException("Endpoint is not registered");
         try {
-            mContextHubProxy.closeEndpointSession(sessionId, (byte) reason);
+            mContextHubProxy.closeEndpointSession(
+                    sessionId, ContextHubServiceUtil.toHalReason(reason));
         } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
             Log.e(TAG, "Exception while calling HAL closeEndpointSession", e);
             throw e;
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
index 77ec51a..957307a 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
@@ -16,7 +16,10 @@
 
 package com.android.server.location.contexthub;
 
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
 import android.Manifest;
+import android.app.AppOpsManager;
 import android.content.Context;
 import android.hardware.contexthub.EndpointInfo;
 import android.hardware.contexthub.HubEndpoint;
@@ -535,4 +538,97 @@
                 return HubEndpoint.REASON_FAILURE;
         }
     }
+
+    /**
+     * Converts a byte integer defined by Reason.aidl to HubEndpoint.Reason values exposed to apps.
+     *
+     * @param reason The Reason.aidl value
+     * @return The converted HubEndpoint.Reason value
+     */
+    /* package */
+    static byte toHalReason(@HubEndpoint.Reason int reason) {
+        switch (reason) {
+            case HubEndpoint.REASON_FAILURE:
+                return Reason.UNSPECIFIED;
+            case HubEndpoint.REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED:
+                return Reason.OPEN_ENDPOINT_SESSION_REQUEST_REJECTED;
+            case HubEndpoint.REASON_CLOSE_ENDPOINT_SESSION_REQUESTED:
+                return Reason.CLOSE_ENDPOINT_SESSION_REQUESTED;
+            case HubEndpoint.REASON_ENDPOINT_INVALID:
+                return Reason.ENDPOINT_INVALID;
+            case HubEndpoint.REASON_ENDPOINT_STOPPED:
+                return Reason.ENDPOINT_GONE;
+            case HubEndpoint.REASON_PERMISSION_DENIED:
+                return Reason.PERMISSION_DENIED;
+            default:
+                Log.w(TAG, "toHalReason: invalid reason: " + reason);
+                return Reason.UNSPECIFIED;
+        }
+    }
+
+    /**
+     * Checks that the module with the provided context/pid/uid has all of the provided permissions.
+     *
+     * @param context The context to validate permissions for
+     * @param pid The PID to validate permissions for
+     * @param uid The UID to validate permissions for
+     * @param permissions The collection of permissions to check
+     * @return true if the module has all of the permissions granted
+     */
+    /* package */
+    static boolean hasPermissions(
+            Context context, int pid, int uid, Collection<String> permissions) {
+        for (String permission : permissions) {
+            if (context.checkPermission(permission, pid, uid) != PERMISSION_GRANTED) {
+                Log.e(TAG, "no permission for " + permission);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Attributes the provided permissions to the package of this client.
+     *
+     * @param appOpsManager The app ops manager to use
+     * @param uid The UID of the module to note permissions for
+     * @param packageName The package name of the module to note permissions for
+     * @param attributionTag The attribution tag of the module to note permissions for
+     * @param permissions The list of permissions covering data the client is about to receive
+     * @param noteMessage The message that should be noted alongside permissions attribution to
+     *     facilitate debugging
+     * @return true if client has ability to use all of the provided permissions
+     */
+    /* package */
+    static boolean notePermissions(
+            AppOpsManager appOpsManager,
+            int uid,
+            String packageName,
+            String attributionTag,
+            List<String> permissions,
+            String noteMessage) {
+        for (String permission : permissions) {
+            int opCode = AppOpsManager.permissionToOpCode(permission);
+            if (opCode != AppOpsManager.OP_NONE) {
+                try {
+                    if (appOpsManager.noteOp(opCode, uid, packageName, attributionTag, noteMessage)
+                            != AppOpsManager.MODE_ALLOWED) {
+                        return false;
+                    }
+                } catch (SecurityException e) {
+                    Log.e(
+                            TAG,
+                            "SecurityException: noteOp for pkg "
+                                    + packageName
+                                    + " opcode "
+                                    + opCode
+                                    + ": "
+                                    + e.getMessage());
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
 }
diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig
index a6948fc..a975da3 100644
--- a/services/core/java/com/android/server/power/feature/power_flags.aconfig
+++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig
@@ -31,7 +31,7 @@
     name: "framework_wakelock_info"
     namespace: "power"
     description: "Feature flag to enable statsd pulling of FrameworkWakelockInfo atoms"
-    bug: "352602149"
+    bug: "380847722"
 }
 
 flag {
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index 7aed33d..16e2029 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -23,6 +23,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER;
 import static android.content.pm.ActivityInfo.isFixedOrientation;
 import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
+import static android.content.pm.ActivityInfo.isFixedOrientationPortrait;
 import static android.content.pm.ActivityInfo.screenOrientationToString;
 
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
@@ -66,6 +67,7 @@
         final boolean shouldCameraCompatControlOrientation =
                 AppCompatCameraPolicy.shouldCameraCompatControlOrientation(mActivityRecord);
         if (hasFullscreenOverride && isIgnoreOrientationRequestEnabled
+                && (isFixedOrientationLandscape(candidate) || isFixedOrientationPortrait(candidate))
                 // Do not override orientation to fullscreen for camera activities.
                 // Fixed-orientation activities are rarely tested in other orientations, and it
                 // often results in sideways or stretched previews. As the camera compat treatment
diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
index d0d3d43..f3b043b 100644
--- a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
@@ -60,7 +60,7 @@
 
     /**
      * The precomputed display insets for resolving configuration. It will be non-null if
-     * {@link #shouldCreateAppCompatDisplayInsets} returns {@code true}.
+     * {@link ActivityRecord#shouldCreateAppCompatDisplayInsets} returns {@code true}.
      */
     @Nullable
     private AppCompatDisplayInsets mAppCompatDisplayInsets;
@@ -84,7 +84,7 @@
     }
 
     /**
-     * @return The {@code true} if the current instance has {@link mAppCompatDisplayInsets} without
+     * @return The {@code true} if the current instance has {@link #mAppCompatDisplayInsets} without
      * considering the inheritance implemented in {@link #getAppCompatDisplayInsets()}
      */
     boolean hasAppCompatDisplayInsetsWithoutInheritance() {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 0eac4f2..60130d1 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2167,13 +2167,14 @@
                 mSystemServiceManager.startServiceFromJar(
                         WIFI_SCANNING_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
                 t.traceEnd();
-                // Start USD service
-                if (android.net.wifi.flags.Flags.usd()) {
-                    t.traceBegin("StartUsd");
-                    mSystemServiceManager.startServiceFromJar(
-                            WIFI_USD_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
-                    t.traceEnd();
-                }
+            }
+
+            if (android.net.wifi.flags.Flags.usd() && context.getResources().getBoolean(
+                    com.android.internal.R.bool.config_deviceSupportsWifiUsd)) {
+                t.traceBegin("StartWifiUsd");
+                mSystemServiceManager.startServiceFromJar(WIFI_USD_SERVICE_CLASS,
+                        WIFI_APEX_SERVICE_JAR_PATH);
+                t.traceEnd();
             }
 
             if (context.getPackageManager().hasSystemFeature(
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 228e32e..c31594a 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -16,6 +16,9 @@
 
 package com.android.server.profcollect;
 
+import static android.content.Intent.ACTION_SCREEN_OFF;
+import static android.content.Intent.ACTION_SCREEN_ON;
+
 import android.Manifest;
 import android.annotation.RequiresPermission;
 import android.app.job.JobInfo;
@@ -32,6 +35,7 @@
 import android.os.Handler;
 import android.os.IBinder.DeathRecipient;
 import android.os.Looper;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
@@ -70,10 +74,11 @@
     private int mUsageSetting;
     private boolean mUploadEnabled;
 
-    private static boolean sVerityEnforced;
-    private boolean mAdbActive;
+    static boolean sVerityEnforced;
+    static boolean sIsInteractive;
+    static boolean sAdbActive;
 
-    private IProfCollectd mIProfcollect;
+    private static IProfCollectd sIProfcollect;
     private static ProfcollectForwardingService sSelfService;
     private final Handler mHandler = new ProfcollectdHandler(IoThread.getHandler().getLooper());
 
@@ -86,17 +91,24 @@
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (INTENT_UPLOAD_PROFILES.equals(intent.getAction())) {
+            if (ACTION_SCREEN_ON.equals(intent.getAction())) {
+                Log.d(LOG_TAG, "Received broadcast that the device became interactive, was "
+                        + sIsInteractive);
+                sIsInteractive = true;
+            } else if (ACTION_SCREEN_OFF.equals(intent.getAction())) {
+                Log.d(LOG_TAG, "Received broadcast that the device became noninteractive, was "
+                        + sIsInteractive);
+                sIsInteractive = false;
+            } else if (INTENT_UPLOAD_PROFILES.equals(intent.getAction())) {
                 Log.d(LOG_TAG, "Received broadcast to pack and upload reports");
                 createAndUploadReport(sSelfService);
-            }
-            if (UsbManager.ACTION_USB_STATE.equals(intent.getAction())) {
+            } else if (UsbManager.ACTION_USB_STATE.equals(intent.getAction())) {
                 boolean isADB = intent.getBooleanExtra(UsbManager.USB_FUNCTION_ADB, false);
                 if (isADB) {
                     boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
                     Log.d(LOG_TAG, "Received broadcast that ADB became " + connected
-                            + ", was " + mAdbActive);
-                    mAdbActive = connected;
+                            + ", was " + sAdbActive);
+                    sAdbActive = connected;
                 }
             }
         }
@@ -129,6 +141,8 @@
             context.getResources().getBoolean(R.bool.config_profcollectReportUploaderEnabled);
 
         final IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_SCREEN_ON);
+        filter.addAction(ACTION_SCREEN_OFF);
         filter.addAction(INTENT_UPLOAD_PROFILES);
         filter.addAction(UsbManager.ACTION_USB_STATE);
         context.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
@@ -153,14 +167,24 @@
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
             UsbManager usbManager = getContext().getSystemService(UsbManager.class);
             if (usbManager == null) {
-                mAdbActive = false;
-                return;
+                sAdbActive = false;
+                Log.d(LOG_TAG, "USBManager is not ready");
+            } else {
+                sAdbActive = ((usbManager.getCurrentFunctions() & UsbManager.FUNCTION_ADB) == 1);
+                Log.d(LOG_TAG, "ADB is " + sAdbActive + " on system startup");
             }
-            mAdbActive = ((usbManager.getCurrentFunctions() & UsbManager.FUNCTION_ADB) == 1);
-            Log.d(LOG_TAG, "ADB is " + mAdbActive + " on system startup");
+
+            PowerManager powerManager = getContext().getSystemService(PowerManager.class);
+            if (powerManager == null) {
+                sIsInteractive = true;
+                Log.d(LOG_TAG, "PowerManager is not ready");
+            } else {
+                sIsInteractive = powerManager.isInteractive();
+                Log.d(LOG_TAG, "Device is interactive " + sIsInteractive + " on system startup");
+            }
         }
         if (phase == PHASE_BOOT_COMPLETED) {
-            if (mIProfcollect == null) {
+            if (sIProfcollect == null) {
                 return;
             }
             BackgroundThread.get().getThreadHandler().post(() -> {
@@ -172,22 +196,22 @@
     }
 
     private void registerProviderStatusCallback() {
-        if (mIProfcollect == null) {
+        if (sIProfcollect == null) {
             return;
         }
         try {
-            mIProfcollect.registerProviderStatusCallback(mProviderStatusCallback);
+            sIProfcollect.registerProviderStatusCallback(mProviderStatusCallback);
         } catch (RemoteException e) {
             Log.e(LOG_TAG, "Failed to register provider status callback: " + e.getMessage());
         }
     }
 
     private boolean serviceHasSupportedTraceProvider() {
-        if (mIProfcollect == null) {
+        if (sIProfcollect == null) {
             return false;
         }
         try {
-            return !mIProfcollect.get_supported_provider().isEmpty();
+            return !sIProfcollect.get_supported_provider().isEmpty();
         } catch (RemoteException e) {
             Log.e(LOG_TAG, "Failed to get supported provider: " + e.getMessage());
             return false;
@@ -209,7 +233,7 @@
                     IProfCollectd.Stub.asInterface(
                             ServiceManager.getServiceOrThrow("profcollectd"));
             profcollectd.asBinder().linkToDeath(new ProfcollectdDeathRecipient(), /*flags*/0);
-            mIProfcollect = profcollectd;
+            sIProfcollect = profcollectd;
             return true;
         } catch (ServiceManager.ServiceNotFoundException | RemoteException e) {
             Log.w(LOG_TAG, "Failed to connect profcollectd binder service.");
@@ -233,7 +257,8 @@
                     break;
                 case MESSAGE_REGISTER_SCHEDULERS:
                     registerObservers();
-                    ProfcollectBGJobService.schedule(getContext());
+                    PeriodicTraceJobService.schedule(getContext());
+                    ReportProcessJobService.schedule(getContext());
                     break;
                 default:
                     throw new AssertionError("Unknown message: " + message);
@@ -246,27 +271,66 @@
         public void binderDied() {
             Log.w(LOG_TAG, "profcollectd has died");
 
-            mIProfcollect = null;
+            sIProfcollect = null;
             tryConnectNativeService();
         }
     }
 
     /**
-     * Background trace process service.
+     * Background report process and upload service.
      */
-    public static class ProfcollectBGJobService extends JobService {
-        // Unique ID in system service
-        private static final int JOB_IDLE_PROCESS = 260817;
+    public static class PeriodicTraceJobService extends JobService {
+        // Unique ID in system server
+        private static final int PERIODIC_TRACE_JOB_ID = 241207;
         private static final ComponentName JOB_SERVICE_NAME = new ComponentName(
                 "android",
-                ProfcollectBGJobService.class.getName());
+                PeriodicTraceJobService.class.getName());
+
+        /**
+         * Attach the service to the system job scheduler.
+         */
+        public static void schedule(Context context) {
+            final int interval = DeviceConfig.getInt(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
+                    "collection_interval", 600);
+            JobScheduler js = context.getSystemService(JobScheduler.class);
+            js.schedule(new JobInfo.Builder(PERIODIC_TRACE_JOB_ID, JOB_SERVICE_NAME)
+                    .setPeriodic(TimeUnit.SECONDS.toMillis(interval))
+                    // PRIORITY_DEFAULT is the highest priority we can request for a periodic job.
+                    .setPriority(JobInfo.PRIORITY_DEFAULT)
+                    .build());
+        }
+
+        @Override
+        public boolean onStartJob(JobParameters params) {
+            if (sIProfcollect != null) {
+                Utils.traceSystem(sIProfcollect, "periodic");
+            }
+            jobFinished(params, false);
+            return true;
+        }
+
+        @Override
+        public boolean onStopJob(JobParameters params) {
+            return false;
+        }
+    }
+
+    /**
+     * Background report process and upload service.
+     */
+    public static class ReportProcessJobService extends JobService {
+        // Unique ID in system server
+        private static final int REPORT_PROCESS_JOB_ID = 260817;
+        private static final ComponentName JOB_SERVICE_NAME = new ComponentName(
+                "android",
+                ReportProcessJobService.class.getName());
 
         /**
          * Attach the service to the system job scheduler.
          */
         public static void schedule(Context context) {
             JobScheduler js = context.getSystemService(JobScheduler.class);
-            js.schedule(new JobInfo.Builder(JOB_IDLE_PROCESS, JOB_SERVICE_NAME)
+            js.schedule(new JobInfo.Builder(REPORT_PROCESS_JOB_ID, JOB_SERVICE_NAME)
                     .setRequiresDeviceIdle(true)
                     .setRequiresCharging(true)
                     .setPeriodic(BG_PROCESS_INTERVAL)
@@ -283,7 +347,6 @@
 
         @Override
         public boolean onStopJob(JobParameters params) {
-            // TODO: Handle this?
             return false;
         }
     }
@@ -311,14 +374,8 @@
     private class AppLaunchObserver extends ActivityMetricsLaunchObserver {
         @Override
         public void onIntentStarted(Intent intent, long timestampNanos) {
-            if (mIProfcollect == null) {
-                return;
-            }
-            if (mAdbActive) {
-                return;
-            }
             if (Utils.withFrequency("applaunch_trace_freq", 5)) {
-                Utils.traceSystem(mIProfcollect, "applaunch");
+                Utils.traceSystem(sIProfcollect, "applaunch");
             }
         }
     }
@@ -336,15 +393,9 @@
     }
 
     private void traceOnDex2oatStart() {
-        if (mIProfcollect == null) {
-            return;
-        }
-        if (mAdbActive) {
-            return;
-        }
         if (Utils.withFrequency("dex2oat_trace_freq", 25)) {
             // Dex2oat could take a while before it starts. Add a short delay before start tracing.
-            Utils.traceSystem(mIProfcollect, "dex2oat", /* delayMs */ 1000);
+            Utils.traceSystem(sIProfcollect, "dex2oat", /* delayMs */ 1000);
         }
     }
 
@@ -367,12 +418,12 @@
 
     private static void createAndUploadReport(ProfcollectForwardingService pfs) {
         BackgroundThread.get().getThreadHandler().post(() -> {
-            if (pfs.mIProfcollect == null) {
+            if (pfs.sIProfcollect == null) {
                 return;
             }
             String reportName;
             try {
-                reportName = pfs.mIProfcollect.report(pfs.mUsageSetting) + ".zip";
+                reportName = pfs.sIProfcollect.report(pfs.mUsageSetting) + ".zip";
             } catch (RemoteException e) {
                 Log.e(LOG_TAG, "Failed to create report: " + e.getMessage());
                 return;
@@ -411,7 +462,7 @@
                     return;
                 }
                 if (Utils.withFrequency("camera_trace_freq", 10)) {
-                    Utils.traceProcess(mIProfcollect,
+                    Utils.traceProcess(sIProfcollect,
                             "camera",
                             "android.hardware.camera.provider",
                             /* durationMs */ 5000);
diff --git a/services/profcollect/src/com/android/server/profcollect/Utils.java b/services/profcollect/src/com/android/server/profcollect/Utils.java
index a8016a0..b754ca1 100644
--- a/services/profcollect/src/com/android/server/profcollect/Utils.java
+++ b/services/profcollect/src/com/android/server/profcollect/Utils.java
@@ -28,28 +28,29 @@
 import java.time.Instant;
 import java.util.concurrent.ThreadLocalRandom;
 
-public final class Utils {
+final class Utils {
 
     private static Instant lastTraceTime = Instant.EPOCH;
     private static final int TRACE_COOLDOWN_SECONDS = 30;
 
-    public static boolean withFrequency(String configName, int defaultFrequency) {
+    static boolean withFrequency(String configName, int defaultFrequency) {
         int threshold = DeviceConfig.getInt(
                 DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT, configName, defaultFrequency);
         int randomNum = ThreadLocalRandom.current().nextInt(100);
         return randomNum < threshold;
     }
 
-    public static boolean traceSystem(IProfCollectd mIProfcollect, String eventName) {
-        if (mIProfcollect == null) {
-            return false;
-        }
-        if (isInCooldownOrReset()) {
+    /**
+     * Request a system-wide trace.
+     * Will be ignored if the device does not meet trace criteria or is being rate limited.
+     */
+    static boolean traceSystem(IProfCollectd iprofcollectd, String eventName) {
+        if (!checkPrerequisites(iprofcollectd)) {
             return false;
         }
         BackgroundThread.get().getThreadHandler().post(() -> {
             try {
-                mIProfcollect.trace_system(eventName);
+                iprofcollectd.trace_system(eventName);
             } catch (RemoteException | ServiceSpecificException e) {
                 Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
             }
@@ -57,16 +58,17 @@
         return true;
     }
 
-    public static boolean traceSystem(IProfCollectd mIProfcollect, String eventName, int delayMs) {
-        if (mIProfcollect == null) {
-            return false;
-        }
-        if (isInCooldownOrReset()) {
+    /**
+     * Request a system-wide trace after a delay.
+     * Will be ignored if the device does not meet trace criteria or is being rate limited.
+     */
+    static boolean traceSystem(IProfCollectd iprofcollectd, String eventName, int delayMs) {
+        if (!checkPrerequisites(iprofcollectd)) {
             return false;
         }
         BackgroundThread.get().getThreadHandler().postDelayed(() -> {
             try {
-                mIProfcollect.trace_system(eventName);
+                iprofcollectd.trace_system(eventName);
             } catch (RemoteException | ServiceSpecificException e) {
                 Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
             }
@@ -74,17 +76,18 @@
         return true;
     }
 
-    public static boolean traceProcess(IProfCollectd mIProfcollect,
+    /**
+     * Request a single-process trace.
+     * Will be ignored if the device does not meet trace criteria or is being rate limited.
+     */
+    static boolean traceProcess(IProfCollectd iprofcollectd,
             String eventName, String processName, int durationMs) {
-        if (mIProfcollect == null) {
-            return false;
-        }
-        if (isInCooldownOrReset()) {
+        if (!checkPrerequisites(iprofcollectd)) {
             return false;
         }
         BackgroundThread.get().getThreadHandler().post(() -> {
             try {
-                mIProfcollect.trace_process(eventName,
+                iprofcollectd.trace_process(eventName,
                         processName,
                         durationMs);
             } catch (RemoteException | ServiceSpecificException e) {
@@ -105,4 +108,16 @@
         }
         return true;
     }
+
+    private static boolean checkPrerequisites(IProfCollectd iprofcollectd) {
+        if (iprofcollectd == null) {
+            return false;
+        }
+        if (isInCooldownOrReset()) {
+            return false;
+        }
+        return ProfcollectForwardingService.sVerityEnforced
+            && !ProfcollectForwardingService.sAdbActive
+            && ProfcollectForwardingService.sIsInteractive;
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index f40d803..db04d39e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -25,6 +25,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED;
+import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS;
 import static com.android.server.RescueParty.DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN;
 import static com.android.server.RescueParty.LEVEL_FACTORY_RESET;
 
@@ -357,11 +359,13 @@
         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
         SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true));
         assertEquals(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation(
-                sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), false);
+                sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
+                MITIGATION_RESULT_SKIPPED);
 
         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
-        assertTrue(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation(
-                sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1));
+        assertEquals(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation(
+                sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
+                MITIGATION_RESULT_SUCCESS);
     }
 
     @Test
@@ -370,7 +374,8 @@
         SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(true));
 
         assertEquals(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation(
-                sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), false);
+                sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
+                MITIGATION_RESULT_SKIPPED);
 
         // Restore the property value initialized in SetUp()
         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index c878799..4ef602f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -25,6 +25,7 @@
 import static com.android.server.wm.WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -34,6 +35,7 @@
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.floatThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.doAnswer;
@@ -50,9 +52,11 @@
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
 import android.os.Looper;
 import android.os.RemoteException;
@@ -60,6 +64,7 @@
 import android.provider.Settings;
 import android.test.mock.MockContentResolver;
 import android.testing.DexmakerShareClassLoaderRule;
+import android.util.DisplayMetrics;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
@@ -79,6 +84,8 @@
 import com.android.server.input.InputManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
+import com.google.common.truth.Expect;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -96,6 +103,9 @@
 @RunWith(AndroidJUnit4.class)
 public class MagnificationControllerTest {
 
+    @Rule
+    public final Expect expect = Expect.create();
+
     private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
     private static final int TEST_SERVICE_ID = 1;
     private static final Region INITIAL_SCREEN_MAGNIFICATION_REGION =
@@ -119,6 +129,8 @@
     @Mock
     private Context mContext;
     @Mock
+    private Resources mResources;
+    @Mock
     private PackageManager mPackageManager;
 
     @Mock
@@ -156,6 +168,7 @@
 
     @Mock
     private DisplayManagerInternal mDisplayManagerInternal;
+    private Display mDisplay;
 
     @Mock
     private Scroller mMockScroller;
@@ -210,6 +223,12 @@
         LocalServices.removeServiceForTest(DisplayManagerInternal.class);
         LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternal);
 
+        DisplayManager displayManager = new DisplayManager(mContext);
+        mDisplay = displayManager.getDisplay(TEST_DISPLAY);
+        when(mContext.getSystemServiceName(DisplayManager.class)).thenReturn(
+                Context.DISPLAY_SERVICE);
+        when(mContext.getSystemService(DisplayManager.class)).thenReturn(displayManager);
+
         mScreenMagnificationController =
                 spy(
                         new FullScreenMagnificationController(
@@ -686,16 +705,19 @@
 
         float currentScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
         while (currentScale < SCALE_MAX_VALUE) {
-            assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
-                    MagnificationController.ZOOM_DIRECTION_IN)).isTrue();
+            mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+                    MagnificationController.ZOOM_DIRECTION_IN);
             final float nextScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
             assertThat(nextScale).isGreaterThan(currentScale);
             currentScale = nextScale;
         }
 
         assertThat(currentScale).isEqualTo(SCALE_MAX_VALUE);
-        assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
-                MagnificationController.ZOOM_DIRECTION_IN)).isFalse();
+        // Trying to scale further does not change the scale.
+        mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+                MagnificationController.ZOOM_DIRECTION_IN);
+        final float finalScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+        assertThat(finalScale).isEqualTo(currentScale);
     }
 
     @Test
@@ -706,16 +728,19 @@
 
         float currentScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
         while (currentScale > SCALE_MIN_VALUE) {
-            assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
-                    MagnificationController.ZOOM_DIRECTION_OUT)).isTrue();
+            mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+                    MagnificationController.ZOOM_DIRECTION_OUT);
             final float nextScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
             assertThat(nextScale).isLessThan(currentScale);
             currentScale = nextScale;
         }
 
         assertThat(currentScale).isEqualTo(SCALE_MIN_VALUE);
-        assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
-                MagnificationController.ZOOM_DIRECTION_OUT)).isFalse();
+        // Trying to scale further does not change the scale.
+        mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+                MagnificationController.ZOOM_DIRECTION_OUT);
+        final float finalScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+        assertThat(finalScale).isEqualTo(currentScale);
     }
 
     @Test
@@ -740,6 +765,121 @@
     }
 
     @Test
+    public void panMagnificationByStep_fullscreenMode_stepSizeAtScale2() throws RemoteException {
+        setMagnificationEnabled(MODE_FULLSCREEN);
+        // At scale 2.0f, each step should be about 40 dpi.
+        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, 2.0f, false);
+        reset(mScreenMagnificationController);
+
+        testFullscreenMagnificationPanWithStepSize(40.0f);
+    }
+
+    @Test
+    public void panMagnificationByStep_fullscreenMode_stepSizeAtScale8() throws RemoteException {
+        setMagnificationEnabled(MODE_FULLSCREEN);
+        // At scale 8.0f, each step should be about 27 dpi.
+        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, 8.0f, false);
+        reset(mScreenMagnificationController);
+
+        testFullscreenMagnificationPanWithStepSize(27.0f);
+    }
+
+    @Test
+    public void panMagnificationByStep_windowMode_stepSizeAtScale2() throws RemoteException {
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2.0f, 100f, 200f);
+
+        testWindowMagnificationPanWithStepSize(40.0f);
+    }
+
+    @Test
+    public void panMagnificationByStep_windowMode_stepSizeAtScale8() throws RemoteException {
+        setMagnificationEnabled(MODE_WINDOW);
+        // At scale 8.0f, each step should be about 27.
+        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, 8.0f, false);
+        reset(mMagnificationConnectionManager);
+
+        testWindowMagnificationPanWithStepSize(27.0f);
+    }
+
+    @Test
+    public void panMagnificationByStep_fullscreenMode_reachesRightEdgeOfScreen()
+            throws RemoteException {
+        setMagnificationEnabled(MODE_FULLSCREEN);
+        // At scale 2.0f, each step should be about 40.
+        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, DEFAULT_SCALE, false);
+        reset(mScreenMagnificationController);
+
+        float currentCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+        float currentCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+
+        DisplayMetrics metrics = new DisplayMetrics();
+        mDisplay.getMetrics(metrics);
+        float expectedStep = 40.0f * metrics.density;
+
+        // Move right, eventually we should reach the edge.
+        int maxNumSteps = (int) (metrics.widthPixels / expectedStep) + 1;
+        int numSteps = 0;
+        while (numSteps < maxNumSteps) {
+            mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
+                    MagnificationController.PAN_DIRECTION_RIGHT);
+            float newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+            float newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+            assertThat(currentCenterY).isEqualTo(newCenterY);
+
+            assertThat(newCenterX).isAtLeast(currentCenterX);
+            if (newCenterX == currentCenterX) {
+                break;
+            }
+
+            currentCenterX = newCenterX;
+            currentCenterY = newCenterY;
+            numSteps++;
+        }
+        assertWithMessage("Still not at edge after panning right " + numSteps
+                + " steps. Current position: " + currentCenterX + "," + currentCenterY)
+                .that(numSteps).isLessThan(maxNumSteps);
+    }
+
+    @Test
+    public void panMagnificationByStep_fullscreenMode_reachesBottomEdgeOfScreen()
+            throws RemoteException {
+        setMagnificationEnabled(MODE_FULLSCREEN);
+        // At scale 2.0f, each step should be about 40.
+        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, DEFAULT_SCALE, false);
+        reset(mScreenMagnificationController);
+
+        float currentCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+        float currentCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+
+        DisplayMetrics metrics = new DisplayMetrics();
+        mDisplay.getMetrics(metrics);
+        float expectedStep = 40.0f * metrics.density;
+
+        // Move down, eventually we should reach the edge.
+        int maxNumSteps = (int) (metrics.heightPixels / expectedStep) + 1;
+        int numSteps = 0;
+        while (numSteps < maxNumSteps) {
+            mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
+                    MagnificationController.PAN_DIRECTION_DOWN);
+            float newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+            float newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+            assertThat(currentCenterX).isEqualTo(newCenterX);
+
+            assertThat(newCenterY).isAtLeast(currentCenterY);
+            if (newCenterY == currentCenterY) {
+                break;
+            }
+
+            currentCenterX = newCenterX;
+            currentCenterY = newCenterY;
+            numSteps++;
+        }
+        assertWithMessage("Still not at edge after panning down "
+                + numSteps + " steps. Current position: " + currentCenterX + "," + currentCenterY)
+                .that(numSteps).isLessThan(maxNumSteps);
+    }
+
+    @Test
     public void enableWindowMode_notifyMagnificationChanged() throws RemoteException {
         setMagnificationEnabled(MODE_WINDOW);
 
@@ -1425,6 +1565,91 @@
         return captor.getValue();
     }
 
+    private void testFullscreenMagnificationPanWithStepSize(float expectedStep) {
+        DisplayMetrics metrics = new DisplayMetrics();
+        mDisplay.getMetrics(metrics);
+        expectedStep *= metrics.density;
+
+        float currentCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+        float currentCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+
+        // Move right.
+        mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
+                MagnificationController.PAN_DIRECTION_RIGHT);
+        float newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+        float newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+        expect.that(currentCenterX).isLessThan(newCenterX);
+        expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep);
+        expect.that(currentCenterY).isEqualTo(newCenterY);
+
+        currentCenterX = newCenterX;
+        currentCenterY = newCenterY;
+
+        // Move left.
+        mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
+                MagnificationController.PAN_DIRECTION_LEFT);
+        newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+        newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+        expect.that(currentCenterX).isGreaterThan(newCenterX);
+        expect.that(currentCenterX - newCenterX).isWithin(0.01f).of(expectedStep);
+        expect.that(currentCenterY).isEqualTo(newCenterY);
+
+        currentCenterX = newCenterX;
+        currentCenterY = newCenterY;
+
+        // Move down.
+        mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
+                MagnificationController.PAN_DIRECTION_DOWN);
+        newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+        newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+        expect.that(currentCenterX).isEqualTo(newCenterX);
+        expect.that(currentCenterY).isLessThan(newCenterY);
+        expect.that(newCenterY - currentCenterY).isWithin(0.1f).of(expectedStep);
+
+        currentCenterX = newCenterX;
+        currentCenterY = newCenterY;
+
+        // Move up.
+        mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
+                MagnificationController.PAN_DIRECTION_UP);
+        newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+        newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+        expect.that(currentCenterX).isEqualTo(newCenterX);
+        expect.that(currentCenterY).isGreaterThan(newCenterY);
+        expect.that(currentCenterY - newCenterY).isWithin(0.01f).of(expectedStep);
+    }
+
+    private void testWindowMagnificationPanWithStepSize(float expectedStepDip)
+            throws RemoteException {
+        DisplayMetrics metrics = new DisplayMetrics();
+        mDisplay.getMetrics(metrics);
+        final float expectedStep = expectedStepDip * metrics.density;
+
+        // Move right.
+        mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
+                MagnificationController.PAN_DIRECTION_RIGHT);
+        verify(mMockConnection.getConnection()).moveWindowMagnifier(eq(TEST_DISPLAY),
+                floatThat(step -> Math.abs(step - expectedStep) < 0.0001), eq(0.0f));
+
+        // Move left.
+        mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
+                MagnificationController.PAN_DIRECTION_LEFT);
+        verify(mMockConnection.getConnection()).moveWindowMagnifier(eq(TEST_DISPLAY),
+                floatThat(step -> Math.abs(expectedStep - step) < 0.0001), eq(0.0f));
+
+        // Move down.
+        mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
+                MagnificationController.PAN_DIRECTION_DOWN);
+        verify(mMockConnection.getConnection()).moveWindowMagnifier(eq(TEST_DISPLAY),
+                eq(0.0f), floatThat(step -> Math.abs(expectedStep - step) < 0.0001));
+
+        // Move up.
+        mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
+                MagnificationController.PAN_DIRECTION_UP);
+        verify(mMockConnection.getConnection()).moveWindowMagnifier(eq(TEST_DISPLAY),
+                eq(0.0f), floatThat(step -> Math.abs(expectedStep - step) < 0.0001));
+    }
+
     private static class WindowMagnificationMgrCallbackDelegate implements
             MagnificationConnectionManager.Callback {
         private MagnificationConnectionManager.Callback mCallback;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 80e86a1..fbd53f7 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -3169,7 +3169,7 @@
 
         doThrow(new SecurityException("no access")).when(mUgmInternal)
                 .checkGrantUriPermission(eq(UID_N_MR1), any(), eq(sound),
-                    anyInt(), eq(Process.myUserHandle().getIdentifier()));
+                    anyInt(), eq(UserHandle.getUserId(UID_N_MR1)));
 
         final NotificationChannel channel = new NotificationChannel("id2", "name2",
                 NotificationManager.IMPORTANCE_DEFAULT);
@@ -3189,7 +3189,7 @@
 
         doThrow(new SecurityException("no access")).when(mUgmInternal)
                 .checkGrantUriPermission(eq(UID_N_MR1), any(), any(),
-                    anyInt(), eq(Process.myUserHandle().getIdentifier()));
+                    anyInt(), eq(UserHandle.getUserId(UID_N_MR1)));
 
         final NotificationChannel channel = new NotificationChannel("id2", "name2",
                 NotificationManager.IMPORTANCE_DEFAULT);
@@ -3208,7 +3208,7 @@
 
         doThrow(new SecurityException("no access")).when(mUgmInternal)
                 .checkGrantUriPermission(eq(UID_N_MR1), any(), any(),
-                    anyInt(), eq(Process.myUserHandle().getIdentifier()));
+                    anyInt(), eq(UserHandle.getUserId(UID_N_MR1)));
 
         final NotificationChannel channel = new NotificationChannel("id2", "name2",
                 NotificationManager.IMPORTANCE_DEFAULT);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
index 09ed9ba..90bf5f0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
@@ -302,15 +302,15 @@
     }
 
     @Test
-    public void testOverrideOrientationIfNeeded_userFullscreenOverride_returnsUser() {
+    public void testOverrideOrientationIfNeeded_userFullscreenOverride_notLetterboxed_unchanged() {
         runTestScenarioWithActivity((robot) -> {
             robot.applyOnActivity((a) -> {
                 a.setShouldApplyUserFullscreenOverride(true);
                 a.setIgnoreOrientationRequest(true);
             });
 
-            robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_UNSPECIFIED,
-                    /* expected */ SCREEN_ORIENTATION_USER);
+            robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_LOCKED,
+                    /* expected */ SCREEN_ORIENTATION_LOCKED);
         });
     }
 
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index fafb0e0..4959cb3 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -782,6 +782,54 @@
                 KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
                 intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
             ),
+            TestData(
+                "META + ALT + 'Down' -> Magnification Pan Down",
+                intArrayOf(
+                    KeyEvent.KEYCODE_CTRL_LEFT,
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_DPAD_DOWN
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_DOWN,
+                intArrayOf(KeyEvent.KEYCODE_DPAD_DOWN),
+                KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + ALT + 'Up' -> Magnification Pan Up",
+                intArrayOf(
+                    KeyEvent.KEYCODE_CTRL_LEFT,
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_DPAD_UP
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_UP,
+                intArrayOf(KeyEvent.KEYCODE_DPAD_UP),
+                KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + ALT + 'Left' -> Magnification Pan Left",
+                intArrayOf(
+                    KeyEvent.KEYCODE_CTRL_LEFT,
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_DPAD_LEFT
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_LEFT,
+                intArrayOf(KeyEvent.KEYCODE_DPAD_LEFT),
+                KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + ALT + 'Right' -> Magnification Pan Right",
+                intArrayOf(
+                    KeyEvent.KEYCODE_CTRL_LEFT,
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_DPAD_RIGHT
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_RIGHT,
+                intArrayOf(KeyEvent.KEYCODE_DPAD_RIGHT),
+                KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
         )
     }
 
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index c64dc72..928e232 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -19,6 +19,7 @@
 import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -1933,12 +1934,12 @@
             return mImpact;
         }
 
-        public boolean onExecuteHealthCheckMitigation(VersionedPackage versionedPackage,
+        public int onExecuteHealthCheckMitigation(VersionedPackage versionedPackage,
                 int failureReason, int mitigationCount) {
             mMitigatedPackages.add(versionedPackage.getPackageName());
             mMitigationCounts.add(mitigationCount);
             mLastFailureReason = failureReason;
-            return true;
+            return MITIGATION_RESULT_SUCCESS;
         }
 
         public String getUniqueIdentifier() {
@@ -1957,11 +1958,10 @@
             return mImpact;
         }
 
-        public boolean onExecuteBootLoopMitigation(int level) {
-            Slog.w("hrm1243", "I'm here " + level);
+        public int onExecuteBootLoopMitigation(int level) {
             mMitigatedBootLoop = true;
             mBootMitigationCounts.add(level);
-            return true;
+            return MITIGATION_RESULT_SUCCESS;
         }
 
         public boolean mitigatedBootLoop() {