Merge "Cancel pending animation when state changed." into udc-dev
diff --git a/Android.bp b/Android.bp
index cff863b..64d2c66 100644
--- a/Android.bp
+++ b/Android.bp
@@ -410,6 +410,7 @@
         "spatializer-aidl-java",
         "audiopolicy-aidl-java",
         "sounddose-aidl-java",
+        "modules-utils-expresslog",
     ],
 }
 
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java b/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java
index f56e1ee..36174c6 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java
@@ -20,6 +20,7 @@
 import android.app.JobSchedulerImpl;
 import android.app.SystemServiceRegistry;
 import android.app.tare.EconomyManager;
+import android.app.tare.IEconomyManager;
 import android.content.Context;
 import android.os.DeviceIdleManager;
 import android.os.IDeviceIdleController;
@@ -58,6 +59,7 @@
                 Context.POWER_EXEMPTION_SERVICE, PowerExemptionManager.class,
                 PowerExemptionManager::new);
         SystemServiceRegistry.registerStaticService(
-                Context.RESOURCE_ECONOMY_SERVICE, EconomyManager.class, EconomyManager::new);
+                Context.RESOURCE_ECONOMY_SERVICE, EconomyManager.class,
+                (b) -> new EconomyManager(IEconomyManager.Stub.asInterface(b)));
     }
 }
diff --git a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
index 581ea7a..0bea028 100644
--- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
+++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
@@ -19,7 +19,9 @@
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.content.Context;
+import android.os.RemoteException;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -30,6 +32,7 @@
  *
  * @hide
  */
+@TestApi
 @SystemService(Context.RESOURCE_ECONOMY_SERVICE)
 public class EconomyManager {
     private static final String TAG = "TARE-" + EconomyManager.class.getSimpleName();
@@ -95,13 +98,17 @@
         }
     }
 
-
+    /** @hide */
+    @TestApi
     public static final int ENABLED_MODE_OFF = 0;
+    /** @hide */
     public static final int ENABLED_MODE_ON = 1;
     /**
      * Go through the motions, tracking events, updating balances and other TARE state values,
      * but don't use TARE to affect actual device behavior.
+     * @hide
      */
+    @TestApi
     public static final int ENABLED_MODE_SHADOW = 2;
 
     /** @hide */
@@ -114,6 +121,7 @@
     public @interface EnabledMode {
     }
 
+    /** @hide */
     public static String enabledModeToString(@EnabledMode int mode) {
         switch (mode) {
             case ENABLED_MODE_OFF: return "ENABLED_MODE_OFF";
@@ -123,11 +131,18 @@
         }
     }
 
+    /** @hide */
+    @TestApi
     public static final String KEY_ENABLE_TARE_MODE = "enable_tare_mode";
+    /** @hide */
     public static final String KEY_ENABLE_POLICY_ALARM = "enable_policy_alarm";
+    /** @hide */
     public static final String KEY_ENABLE_POLICY_JOB_SCHEDULER = "enable_policy_job";
+    /** @hide */
     public static final int DEFAULT_ENABLE_TARE_MODE = ENABLED_MODE_OFF;
+    /** @hide */
     public static final boolean DEFAULT_ENABLE_POLICY_ALARM = true;
+    /** @hide */
     public static final boolean DEFAULT_ENABLE_POLICY_JOB_SCHEDULER = true;
 
     // Keys for AlarmManager TARE factors
@@ -612,4 +627,27 @@
     public static final long DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE_CAKES = arcToCake(1);
     /** @hide */
     public static final long DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE_CAKES = arcToCake(60);
+
+    //////// APIs below ////////
+
+    private final IEconomyManager mService;
+
+    /** @hide */
+    public EconomyManager(IEconomyManager service) {
+        mService = service;
+    }
+
+    /**
+     * Returns the current enabled status of TARE.
+     * @hide
+     */
+    @EnabledMode
+    @TestApi
+    public int getEnabledMode() {
+        try {
+            return mService.getEnabledMode();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/apex/jobscheduler/framework/java/android/app/tare/IEconomyManager.aidl b/apex/jobscheduler/framework/java/android/app/tare/IEconomyManager.aidl
index bb15011..2be0db7 100644
--- a/apex/jobscheduler/framework/java/android/app/tare/IEconomyManager.aidl
+++ b/apex/jobscheduler/framework/java/android/app/tare/IEconomyManager.aidl
@@ -21,4 +21,5 @@
   * {@hide}
   */
 interface IEconomyManager {
+    int getEnabledMode();
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index aef9dd0..3aec8ba 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1588,12 +1588,12 @@
         final ArrayMap<String, List<JobInfo>> outMap = new ArrayMap<>();
         synchronized (mLock) {
             ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
-            // Write out for loop to avoid addAll() creating an Iterator.
+            // Write out for loop to avoid creating an Iterator.
             for (int i = jobs.size() - 1; i >= 0; i--) {
                 final JobStatus job = jobs.valueAt(i);
                 List<JobInfo> outList = outMap.get(job.getNamespace());
                 if (outList == null) {
-                    outList = new ArrayList<JobInfo>(jobs.size());
+                    outList = new ArrayList<>();
                     outMap.put(job.getNamespace(), outList);
                 }
 
@@ -1606,7 +1606,7 @@
     private List<JobInfo> getPendingJobsInNamespace(int uid, @Nullable String namespace) {
         synchronized (mLock) {
             ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
-            ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
+            ArrayList<JobInfo> outList = new ArrayList<>();
             // Write out for loop to avoid addAll() creating an Iterator.
             for (int i = jobs.size() - 1; i >= 0; i--) {
                 final JobStatus job = jobs.valueAt(i);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
index fc60228..ba62e96 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
@@ -31,7 +31,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.expresslog.Counter;
+import com.android.modules.expresslog.Counter;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateControllerProto;
 
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 7f6a75e..c707069 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -1351,6 +1351,11 @@
         }
 
         @Override
+        public int getEnabledMode() {
+            return InternalResourceService.this.getEnabledMode();
+        }
+
+        @Override
         public int handleShellCommand(@NonNull ParcelFileDescriptor in,
                 @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
                 @NonNull String[] args) {
diff --git a/boot/boot-image-profile.txt b/boot/boot-image-profile.txt
index 996c388..aebace5 100644
--- a/boot/boot-image-profile.txt
+++ b/boot/boot-image-profile.txt
@@ -33583,8 +33583,8 @@
 Lcom/android/internal/dynamicanimation/animation/Force;
 Lcom/android/internal/dynamicanimation/animation/SpringAnimation;
 Lcom/android/internal/dynamicanimation/animation/SpringForce;
-Lcom/android/internal/expresslog/Counter;
-Lcom/android/internal/expresslog/Utils;
+Lcom/android/modules/expresslog/Counter;
+Lcom/android/modules/expresslog/Utils;
 Lcom/android/internal/graphics/ColorUtils$ContrastCalculator;
 Lcom/android/internal/graphics/ColorUtils;
 Lcom/android/internal/graphics/SfVsyncFrameCallbackProvider;
diff --git a/boot/preloaded-classes b/boot/preloaded-classes
index 21ae134..4293caf 100644
--- a/boot/preloaded-classes
+++ b/boot/preloaded-classes
@@ -10784,8 +10784,8 @@
 com.android.internal.dynamicanimation.animation.Force
 com.android.internal.dynamicanimation.animation.SpringAnimation
 com.android.internal.dynamicanimation.animation.SpringForce
-com.android.internal.expresslog.Counter
-com.android.internal.expresslog.Utils
+com.android.modules.expresslog.Counter
+com.android.modules.expresslog.Utils
 com.android.internal.graphics.ColorUtils$ContrastCalculator
 com.android.internal.graphics.ColorUtils
 com.android.internal.graphics.SfVsyncFrameCallbackProvider
diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt
index 3cc9908..bb07487 100644
--- a/config/boot-image-profile.txt
+++ b/config/boot-image-profile.txt
@@ -43717,8 +43717,8 @@
 Lcom/android/internal/dynamicanimation/animation/Force;
 Lcom/android/internal/dynamicanimation/animation/SpringAnimation;
 Lcom/android/internal/dynamicanimation/animation/SpringForce;
-Lcom/android/internal/expresslog/Counter;
-Lcom/android/internal/expresslog/Utils;
+Lcom/android/modules/expresslog/Counter;
+Lcom/android/modules/expresslog/Utils;
 Lcom/android/internal/graphics/ColorUtils$ContrastCalculator;
 Lcom/android/internal/graphics/ColorUtils;
 Lcom/android/internal/graphics/SfVsyncFrameCallbackProvider;
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 8e50fe8..1812c2b 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -10815,8 +10815,8 @@
 com.android.internal.dynamicanimation.animation.Force
 com.android.internal.dynamicanimation.animation.SpringAnimation
 com.android.internal.dynamicanimation.animation.SpringForce
-com.android.internal.expresslog.Counter
-com.android.internal.expresslog.Utils
+com.android.modules.expresslog.Counter
+com.android.modules.expresslog.Utils
 com.android.internal.graphics.ColorUtils$ContrastCalculator
 com.android.internal.graphics.ColorUtils
 com.android.internal.graphics.SfVsyncFrameCallbackProvider
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 7d81a95..2d9a99c 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -8,8 +8,6 @@
     field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS";
     field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA";
     field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE";
-    field public static final String BODY_SENSORS_WRIST_TEMPERATURE = "android.permission.BODY_SENSORS_WRIST_TEMPERATURE";
-    field public static final String BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND = "android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND";
     field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
     field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS";
     field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
@@ -779,6 +777,17 @@
 
 }
 
+package android.app.tare {
+
+  public class EconomyManager {
+    method public int getEnabledMode();
+    field public static final int ENABLED_MODE_OFF = 0; // 0x0
+    field public static final int ENABLED_MODE_SHADOW = 2; // 0x2
+    field public static final String KEY_ENABLE_TARE_MODE = "enable_tare_mode";
+  }
+
+}
+
 package android.app.usage {
 
   public class StorageStatsManager {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 1dddf06..3312294 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1450,9 +1450,8 @@
     public static final int OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD =
             AppProtoEnums.APP_OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD;
 
-    /** @hide Access to wrist temperature sensors. */
-    public static final int OP_BODY_SENSORS_WRIST_TEMPERATURE =
-            AppProtoEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE;
+    // App op deprecated/removed.
+    private static final int OP_DEPRECATED_2 = AppProtoEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE;
 
     /**
      * Send an intent to launch instead of posting the notification to the status bar.
@@ -1619,7 +1618,6 @@
             OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION,
             OPSTR_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
             OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
-            OPSTR_BODY_SENSORS_WRIST_TEMPERATURE,
             OPSTR_USE_FULL_SCREEN_INTENT,
             OPSTR_CAMERA_SANDBOXED,
             OPSTR_RECORD_AUDIO_SANDBOXED
@@ -2221,11 +2219,10 @@
             "android:capture_consentless_bugreport_on_userdebug_build";
 
     /**
-     * Access to wrist temperature body sensors.
+     * App op deprecated/removed.
      * @hide
      */
-    public static final String OPSTR_BODY_SENSORS_WRIST_TEMPERATURE =
-            "android:body_sensors_wrist_temperature";
+    public static final String OPSTR_DEPRECATED_2 = "android:deprecated_2";
 
     /**
      * Send an intent to launch instead of posting the notification to the status bar.
@@ -2343,7 +2340,6 @@
             OP_READ_MEDIA_VISUAL_USER_SELECTED,
             OP_FOREGROUND_SERVICE_SPECIAL_USE,
             OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
-            OP_BODY_SENSORS_WRIST_TEMPERATURE,
             OP_USE_FULL_SCREEN_INTENT
     };
 
@@ -2763,11 +2759,8 @@
                 "CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD")
                 .setPermission(Manifest.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD)
                 .build(),
-        new AppOpInfo.Builder(OP_BODY_SENSORS_WRIST_TEMPERATURE,
-                OPSTR_BODY_SENSORS_WRIST_TEMPERATURE,
-                "BODY_SENSORS_WRIST_TEMPERATURE")
-                .setPermission(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE)
-                .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_DEPRECATED_2, OPSTR_DEPRECATED_2, "DEPRECATED_2")
+                .setDefaultMode(AppOpsManager.MODE_IGNORED).build(),
         new AppOpInfo.Builder(OP_USE_FULL_SCREEN_INTENT, OPSTR_USE_FULL_SCREEN_INTENT,
                 "USE_FULL_SCREEN_INTENT").setPermission(Manifest.permission.USE_FULL_SCREEN_INTENT)
                 .build(),
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index be012cf..c0c59a2 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -473,7 +473,6 @@
             new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
                 new RegularPermission(Manifest.permission.ACTIVITY_RECOGNITION),
                 new RegularPermission(Manifest.permission.BODY_SENSORS),
-                new RegularPermission(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE),
                 new RegularPermission(Manifest.permission.HIGH_SAMPLING_RATE_SENSORS),
             }, false),
             FGS_TYPE_PERM_ENFORCEMENT_FLAG_HEALTH /* permissionEnforcementFlag */,
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index ee24263..2b15589 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -220,6 +220,20 @@
     void notifyGoingToSleep(int x, int y, in Bundle extras);
 
     /**
+     * Called when the screen has been fully turned on and is visible.
+     *
+     * @hide
+     */
+    void notifyScreenTurnedOn(int displayId);
+
+    /**
+     * Called when the screen starts turning on.
+     *
+     * @hide
+     */
+    void notifyScreenTurningOn(int displayId);
+
+    /**
      * Sets the wallpaper dim amount between [0f, 1f] which would be blended with the system default
      * dimming. 0f doesn't add any additional dimming and 1f makes the wallpaper fully black.
      *
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index c131ce5..e31486f 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -2354,8 +2354,7 @@
                 return mUiAutomation;
             }
             if (mustCreateNewAutomation) {
-                mUiAutomation = new UiAutomation(getTargetContext().getMainLooper(),
-                        mUiAutomationConnection);
+                mUiAutomation = new UiAutomation(getTargetContext(), mUiAutomationConnection);
             } else {
                 mUiAutomation.disconnect();
             }
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 1df8602..bc5f7f4 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -110,6 +110,9 @@
             "options": [
                 {
                     "exclude-annotation": "androidx.test.filters.FlakyTest"
+                },
+                {
+                    "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceStressTest"
                 }
             ],
             "file_patterns": ["(/|^)VoiceInteract[^/]*"]
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 658e084..247d5bc 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -16,6 +16,8 @@
 
 package android.app;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import android.accessibilityservice.AccessibilityGestureEvent;
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityService.Callbacks;
@@ -30,6 +32,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -45,6 +48,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.ArraySet;
 import android.util.DebugUtils;
 import android.util.Log;
@@ -69,8 +73,10 @@
 import android.view.inputmethod.EditorInfo;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
 import com.android.internal.inputmethod.RemoteAccessibilityInputConnection;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.function.pooled.PooledLambda;
 
 import libcore.io.IoUtils;
@@ -202,6 +208,8 @@
 
     private final IUiAutomationConnection mUiAutomationConnection;
 
+    private final int mDisplayId;
+
     private HandlerThread mRemoteCallbackThread;
 
     private IAccessibilityServiceClient mClient;
@@ -261,24 +269,49 @@
 
     /**
      * Creates a new instance that will handle callbacks from the accessibility
+     * layer on the thread of the provided context main looper and perform requests for privileged
+     * operations on the provided connection, and filtering display-related features to the display
+     * associated with the context (or the user running the test, on devices that
+     * {@link UserManager#isVisibleBackgroundUsersSupported() support visible background users}).
+     *
+     * @param context the context associated with the automation
+     * @param connection The connection for performing privileged operations.
+     *
+     * @hide
+     */
+    public UiAutomation(Context context, IUiAutomationConnection connection) {
+        this(getDisplayId(context), context.getMainLooper(), connection);
+    }
+
+    /**
+     * Creates a new instance that will handle callbacks from the accessibility
      * layer on the thread of the provided looper and perform requests for privileged
      * operations on the provided connection.
      *
      * @param looper The looper on which to execute accessibility callbacks.
      * @param connection The connection for performing privileged operations.
      *
+     * @deprecated use {@link #UiAutomation(Context, IUiAutomationConnection)} instead
+     *
      * @hide
      */
+    @Deprecated
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public UiAutomation(Looper looper, IUiAutomationConnection connection) {
-        if (looper == null) {
-            throw new IllegalArgumentException("Looper cannot be null!");
-        }
-        if (connection == null) {
-            throw new IllegalArgumentException("Connection cannot be null!");
-        }
+        this(DEFAULT_DISPLAY, looper, connection);
+        Log.w(LOG_TAG, "Created with deprecatead constructor, assumes DEFAULT_DISPLAY");
+    }
+
+    private UiAutomation(int displayId, Looper looper, IUiAutomationConnection connection) {
+        Preconditions.checkArgument(looper != null, "Looper cannot be null!");
+        Preconditions.checkArgument(connection != null, "Connection cannot be null!");
+
         mLocalCallbackHandler = new Handler(looper);
         mUiAutomationConnection = connection;
+        mDisplayId = displayId;
+
+        Log.i(LOG_TAG, "Initialized for user " + Process.myUserHandle().getIdentifier()
+                + " on display " + mDisplayId);
     }
 
     /**
@@ -719,8 +752,14 @@
     }
 
     /**
-     * Gets the windows on the screen of the default display. This method returns only the windows
-     * that a sighted user can interact with, as opposed to all windows.
+     * Gets the windows on the screen associated with the {@link UiAutomation} context (usually the
+     * {@link android.view.Display#DEFAULT_DISPLAY default display).
+     *
+     * <p>
+     * This method returns only the windows that a sighted user can interact with, as opposed to
+     * all windows.
+
+     * <p>
      * For example, if there is a modal dialog shown and the user cannot touch
      * anything behind it, then only the modal window will be reported
      * (assuming it is the top one). For convenience the returned windows
@@ -730,21 +769,23 @@
      * <strong>Note:</strong> In order to access the windows you have to opt-in
      * to retrieve the interactive windows by setting the
      * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag.
-     * </p>
      *
      * @return The windows if there are windows such, otherwise an empty list.
      * @throws IllegalStateException If the connection to the accessibility subsystem is not
      *            established.
      */
     public List<AccessibilityWindowInfo> getWindows() {
+        if (DEBUG) {
+            Log.d(LOG_TAG, "getWindows(): returning windows for display " + mDisplayId);
+        }
         final int connectionId;
         synchronized (mLock) {
             throwIfNotConnectedLocked();
             connectionId = mConnectionId;
         }
         // Calling out without a lock held.
-        return AccessibilityInteractionClient.getInstance()
-                .getWindows(connectionId);
+        return AccessibilityInteractionClient.getInstance().getWindowsOnDisplay(connectionId,
+                mDisplayId);
     }
 
     /**
@@ -1112,8 +1153,10 @@
      * @return The screenshot bitmap on success, null otherwise.
      */
     public Bitmap takeScreenshot() {
-        Display display = DisplayManagerGlobal.getInstance()
-                .getRealDisplay(Display.DEFAULT_DISPLAY);
+        if (DEBUG) {
+            Log.d(LOG_TAG, "Taking screenshot of display " + mDisplayId);
+        }
+        Display display = DisplayManagerGlobal.getInstance().getRealDisplay(mDisplayId);
         Point displaySize = new Point();
         display.getRealSize(displaySize);
 
@@ -1126,10 +1169,12 @@
             screenShot = mUiAutomationConnection.takeScreenshot(
                     new Rect(0, 0, displaySize.x, displaySize.y));
             if (screenShot == null) {
+                Log.e(LOG_TAG, "mUiAutomationConnection.takeScreenshot() returned null for display "
+                        + mDisplayId);
                 return null;
             }
         } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while taking screenshot!", re);
+            Log.e(LOG_TAG, "Error while taking screenshot of display " + mDisplayId, re);
             return null;
         }
 
@@ -1509,6 +1554,14 @@
         return executeShellCommandInternal(command, true /* includeStderr */);
     }
 
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
     private ParcelFileDescriptor[] executeShellCommandInternal(
             String command, boolean includeStderr) {
         warnIfBetterCommand(command);
@@ -1564,6 +1617,7 @@
         final StringBuilder stringBuilder = new StringBuilder();
         stringBuilder.append("UiAutomation@").append(Integer.toHexString(hashCode()));
         stringBuilder.append("[id=").append(mConnectionId);
+        stringBuilder.append(", displayId=").append(mDisplayId);
         stringBuilder.append(", flags=").append(mFlags);
         stringBuilder.append("]");
         return stringBuilder.toString();
@@ -1601,6 +1655,55 @@
         return (mFlags & UiAutomation.FLAG_DONT_USE_ACCESSIBILITY) == 0;
     }
 
+    /**
+     * Gets the display id associated with the UiAutomation context.
+     *
+     * <p><b>NOTE: </b> must be a static method because it's called from a constructor to call
+     * another one.
+     */
+    private static int getDisplayId(Context context) {
+        Preconditions.checkArgument(context != null, "Context cannot be null!");
+
+        UserManager userManager = context.getSystemService(UserManager.class);
+        // TODO(b/255426725): given that this is a temporary solution until a11y supports multiple
+        // users, the display is only set on devices that support that
+        if (!userManager.isVisibleBackgroundUsersSupported()) {
+            return DEFAULT_DISPLAY;
+        }
+
+        int displayId = context.getDisplayId();
+        if (displayId == Display.INVALID_DISPLAY) {
+            // Shouldn't happen, but we better handle it
+            Log.e(LOG_TAG, "UiAutomation created UI context with invalid display id, assuming it's"
+                    + " running in the display assigned to the user");
+            return getMainDisplayIdAssignedToUser(context, userManager);
+        }
+
+        if (displayId != DEFAULT_DISPLAY) {
+            if (DEBUG) {
+                Log.d(LOG_TAG, "getDisplayId(): returning context's display (" + displayId + ")");
+            }
+            // Context is explicitly setting the display, so we respect that...
+            return displayId;
+        }
+        // ...otherwise, we need to get the display the test's user is running on
+        int userDisplayId = getMainDisplayIdAssignedToUser(context, userManager);
+        if (DEBUG) {
+            Log.d(LOG_TAG, "getDisplayId(): returning user's display (" + userDisplayId + ")");
+        }
+        return userDisplayId;
+    }
+
+    private static int getMainDisplayIdAssignedToUser(Context context, UserManager userManager) {
+        if (!userManager.isUserVisible()) {
+            // Should also not happen, but ...
+            Log.e(LOG_TAG, "User (" + context.getUserId() + ") is not visible, using "
+                    + "DEFAULT_DISPLAY");
+            return DEFAULT_DISPLAY;
+        }
+        return userManager.getMainDisplayIdAssignedToUser();
+    }
+
     private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
 
         public IAccessibilityServiceClientImpl(Looper looper, int generationId) {
@@ -1621,6 +1724,7 @@
                     if (DEBUG) {
                         Log.d(LOG_TAG, "init(): connectionId=" + connectionId + ", windowToken="
                                 + windowToken + ", user=" + Process.myUserHandle()
+                                + ", UiAutomation.mDisplay=" + UiAutomation.this.mDisplayId
                                 + ", mGenerationId=" + mGenerationId
                                 + ", UiAutomation.mGenerationId="
                                 + UiAutomation.this.mGenerationId);
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 13e800e..d96a9d1 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -22,6 +22,7 @@
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.graphics.Bitmap;
@@ -117,7 +118,8 @@
                 throw new IllegalStateException("Already connected.");
             }
             mOwningUid = Binder.getCallingUid();
-            registerUiTestAutomationServiceLocked(client, flags);
+            registerUiTestAutomationServiceLocked(client,
+                    Binder.getCallingUserHandle().getIdentifier(), flags);
             storeRotationStateLocked();
         }
     }
@@ -553,7 +555,7 @@
     }
 
     private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client,
-            int flags) {
+            @UserIdInt int userId, int flags) {
         IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
                 ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
         final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
@@ -571,10 +573,11 @@
         try {
             // Calling out with a lock held is fine since if the system
             // process is gone the client calling in will be killed.
-            manager.registerUiTestAutomationService(mToken, client, info, flags);
+            manager.registerUiTestAutomationService(mToken, client, info, userId, flags);
             mClient = client;
         } catch (RemoteException re) {
-            throw new IllegalStateException("Error while registering UiTestAutomationService.", re);
+            throw new IllegalStateException("Error while registering UiTestAutomationService for "
+                    + "user " + userId + ".", re);
         }
     }
 
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 5feda78..ad68866 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -1310,6 +1310,10 @@
      * {@link Surface}, submitting a reprocess {@link CaptureRequest} with multiple
      * output targets will result in a {@link CaptureFailure}.
      *
+     * From Android 14 onward, {@link CaptureRequest#CONTROL_CAPTURE_INTENT} will be set to
+     * {@link CameraMetadata#CONTROL_CAPTURE_INTENT_STILL_CAPTURE} by default. Prior to Android 14,
+     * apps will need to explicitly set this key themselves.
+     *
      * @param inputResult The capture result of the output image or one of the output images used
      *                       to generate the reprocess input image for this capture request.
      *
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index cb1efe8..f2d8caa 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -26,6 +26,7 @@
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.hardware.camera2.CameraMetadata;
 import android.hardware.camera2.CameraOfflineSession;
 import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.CaptureRequest;
@@ -861,8 +862,13 @@
             CameraMetadataNative resultMetadata = new
                     CameraMetadataNative(inputResult.getNativeCopy());
 
-            return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true,
-                    inputResult.getSessionId(), getId(), /*physicalCameraIdSet*/ null);
+            CaptureRequest.Builder builder = new CaptureRequest.Builder(resultMetadata,
+                    /*reprocess*/true, inputResult.getSessionId(), getId(),
+                    /*physicalCameraIdSet*/ null);
+            builder.set(CaptureRequest.CONTROL_CAPTURE_INTENT,
+                    CameraMetadata.CONTROL_CAPTURE_INTENT_STILL_CAPTURE);
+
+            return builder;
         }
     }
 
diff --git a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
index dcc3369..4663730 100644
--- a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
+++ b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
@@ -42,12 +42,6 @@
     void onGenericSoundTriggerDetected(in SoundTrigger.GenericRecognitionEvent recognitionEvent);
 
     /**
-     * Called when the detection fails due to an error.
-     *
-     * @param status The error code that was seen.
-     */
-    void onError(int status);
-    /**
      * Called when the recognition is paused temporarily for some reason.
      */
     void onRecognitionPaused();
@@ -55,4 +49,28 @@
      * Called when the recognition is resumed after it was temporarily paused.
      */
     void onRecognitionResumed();
+
+    // Error callbacks to follow
+    /**
+     * Called when this recognition has been preempted by another.
+     */
+    void onPreempted();
+
+    /**
+     * Called when the underlying ST module service has died.
+     */
+    void onModuleDied();
+
+    /**
+     * Called when the service failed to gracefully resume recognition following a pause.
+     * @param status - The received error code.
+     */
+    void onResumeFailed(int status);
+
+    /**
+     * Called when the service failed to pause recognition when required.
+     * TODO(b/276507281) Remove. This should never happen, so we should abort instead.
+     * @param status - The received error code.
+     */
+    void onPauseFailed(int status);
 }
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 9f9c222..7383e63 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -493,7 +493,7 @@
          * @hide
          */
         @TestApi
-        public static final int RESOURCES_SDK_INT = SDK_INT + ACTIVE_CODENAMES.length;
+        public static final int RESOURCES_SDK_INT = SDK_INT;
 
         /**
          * The current lowest supported value of app target SDK. Applications targeting
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 8688a18..24c96ea 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -1573,16 +1573,6 @@
         }
 
         @Override
-        public void onError(int status) {
-            Slog.i(TAG, "onError: " + status);
-            // TODO(b/271534248): This is a workaround before the sound trigger uses the new error
-            // method.
-            Message.obtain(mHandler, MSG_DETECTION_SOUND_TRIGGER_FAILURE,
-                    new SoundTriggerFailure(SoundTriggerFailure.ERROR_CODE_UNKNOWN,
-                            "Sound trigger error")).sendToTarget();
-        }
-
-        @Override
         public void onHotwordDetectionServiceFailure(
                 HotwordDetectionServiceFailure hotwordDetectionServiceFailure) {
             Slog.v(TAG, "onHotwordDetectionServiceFailure: " + hotwordDetectionServiceFailure);
@@ -1605,6 +1595,12 @@
         }
 
         @Override
+        public void onSoundTriggerFailure(SoundTriggerFailure soundTriggerFailure) {
+            Message.obtain(mHandler, MSG_DETECTION_SOUND_TRIGGER_FAILURE,
+                    Objects.requireNonNull(soundTriggerFailure)).sendToTarget();
+        }
+
+        @Override
         public void onUnknownFailure(String errorMessage) throws RemoteException {
             Slog.v(TAG, "onUnknownFailure: " + errorMessage);
             Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE,
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index eac7aee..7ab4faf 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -234,14 +234,6 @@
         }
 
         @Override
-        public void onError(int status) throws RemoteException {
-            if (DEBUG) {
-                Slog.i(TAG, "Ignored #onError (" + status + ") event");
-            }
-            // TODO: Check if we still need to implement this method with DetectorFailure mechanism.
-        }
-
-        @Override
         public void onHotwordDetectionServiceFailure(
                 HotwordDetectionServiceFailure hotwordDetectionServiceFailure)
                 throws RemoteException {
@@ -265,6 +257,13 @@
         }
 
         @Override
+        public void onSoundTriggerFailure(SoundTriggerFailure onSoundTriggerFailure)
+                throws RemoteException {
+            // It should never be called here.
+            Slog.wtf(TAG, "Unexpected STFailure in software detector: " + onSoundTriggerFailure);
+        }
+
+        @Override
         public void onUnknownFailure(String errorMessage) throws RemoteException {
             Slog.v(TAG, "onUnknownFailure: " + errorMessage);
             Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
diff --git a/core/java/android/service/voice/SoundTriggerFailure.java b/core/java/android/service/voice/SoundTriggerFailure.java
index 5560800..2ce5e5d 100644
--- a/core/java/android/service/voice/SoundTriggerFailure.java
+++ b/core/java/android/service/voice/SoundTriggerFailure.java
@@ -73,18 +73,28 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface SoundTriggerErrorCode {}
 
-    private int mErrorCode = ERROR_CODE_UNKNOWN;
-    private String mErrorMessage = "Unknown";
+    private final int mErrorCode;
+    private final String mErrorMessage;
 
     /**
      * @hide
      */
     @TestApi
-    public SoundTriggerFailure(int errorCode, @NonNull String errorMessage) {
+    public SoundTriggerFailure(@SoundTriggerErrorCode int errorCode,
+            @NonNull String errorMessage) {
         if (TextUtils.isEmpty(errorMessage)) {
             throw new IllegalArgumentException("errorMessage is empty or null.");
         }
-        mErrorCode = errorCode;
+        switch (errorCode) {
+            case ERROR_CODE_UNKNOWN:
+            case ERROR_CODE_MODULE_DIED:
+            case ERROR_CODE_RECOGNITION_RESUME_FAILED:
+            case ERROR_CODE_UNEXPECTED_PREEMPTION:
+                mErrorCode = errorCode;
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid ErrorCode: " + errorCode);
+        }
         mErrorMessage = errorMessage;
     }
 
@@ -110,13 +120,14 @@
     @FailureSuggestedAction.FailureSuggestedActionDef
     public int getSuggestedAction() {
         switch (mErrorCode) {
+            case ERROR_CODE_UNKNOWN:
             case ERROR_CODE_MODULE_DIED:
             case ERROR_CODE_UNEXPECTED_PREEMPTION:
                 return FailureSuggestedAction.RECREATE_DETECTOR;
             case ERROR_CODE_RECOGNITION_RESUME_FAILED:
                 return FailureSuggestedAction.RESTART_RECOGNITION;
             default:
-                return FailureSuggestedAction.NONE;
+                throw new AssertionError("Unexpected error code");
         }
     }
 
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index b4f5ff1..93b7964 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -391,12 +391,6 @@
         }
 
         @Override
-        public void onError(int status) throws RemoteException {
-            Slog.v(TAG, "Initialization Error: (" + status + ")");
-            // Do nothing
-        }
-
-        @Override
         public void onHotwordDetectionServiceFailure(
                 HotwordDetectionServiceFailure hotwordDetectionServiceFailure)
                 throws RemoteException {
@@ -420,6 +414,11 @@
         }
 
         @Override
+        public void onSoundTriggerFailure(SoundTriggerFailure soundTriggerFailure) {
+            Slog.wtf(TAG, "Unexpected STFailure in VisualQueryDetector" + soundTriggerFailure);
+        }
+
+        @Override
         public void onUnknownFailure(String errorMessage) throws RemoteException {
             Slog.v(TAG, "onUnknownFailure: " + errorMessage);
             Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index 4c51be0..f1ae22e 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -32,6 +32,8 @@
     oneway void setDisplayPadding(in Rect padding);
     @UnsupportedAppUsage
     oneway void setVisibility(boolean visible);
+    oneway void onScreenTurningOn();
+    oneway void onScreenTurnedOn();
     oneway void setInAmbientMode(boolean inAmbientDisplay, long animationDuration);
     @UnsupportedAppUsage
     oneway void dispatchPointer(in MotionEvent event);
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 0b947fc..77bbeb5 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -168,6 +168,7 @@
     private static final int MSG_ZOOM = 10100;
     private static final int MSG_RESIZE_PREVIEW = 10110;
     private static final int MSG_REPORT_SHOWN = 10150;
+    private static final int MSG_UPDATE_SCREEN_TURNING_ON = 10170;
     private static final int MSG_UPDATE_DIMMING = 10200;
     private static final int MSG_WALLPAPER_FLAGS_CHANGED = 10210;
 
@@ -213,6 +214,16 @@
 
         boolean mInitializing = true;
         boolean mVisible;
+        /**
+         * Whether the screen is turning on.
+         * After the display is powered on, brightness is initially off. It is turned on only after
+         * all windows have been drawn, and sysui notifies that it's ready (See
+         * {@link com.android.internal.policy.IKeyguardDrawnCallback}).
+         * As some wallpapers use visibility as a signal to start animations, this makes sure
+         * {@link Engine#onVisibilityChanged} is invoked only when the display is both on and
+         * visible (with brightness on).
+         */
+        private boolean mIsScreenTurningOn;
         boolean mReportedVisible;
         boolean mDestroyed;
         // Set to true after receiving WallpaperManager#COMMAND_FREEZE. It's reset back to false
@@ -1018,6 +1029,7 @@
                     out.print(" mDestroyed="); out.println(mDestroyed);
             out.print(prefix); out.print("mVisible="); out.print(mVisible);
                     out.print(" mReportedVisible="); out.println(mReportedVisible);
+                    out.print(" mIsScreenTurningOn="); out.println(mIsScreenTurningOn);
             out.print(prefix); out.print("mDisplay="); out.println(mDisplay);
             out.print(prefix); out.print("mCreated="); out.print(mCreated);
                     out.print(" mSurfaceCreated="); out.print(mSurfaceCreated);
@@ -1549,6 +1561,13 @@
             }
         }
 
+        void onScreenTurningOnChanged(boolean isScreenTurningOn) {
+            if (!mDestroyed) {
+                mIsScreenTurningOn = isScreenTurningOn;
+                reportVisibility(false);
+            }
+        }
+
         void doVisibilityChanged(boolean visible) {
             if (!mDestroyed) {
                 mVisible = visible;
@@ -1565,9 +1584,10 @@
                 return;
             }
             if (!mDestroyed) {
-                mDisplayState = mDisplay == null ? Display.STATE_UNKNOWN :
-                        mDisplay.getCommittedState();
-                boolean visible = mVisible && mDisplayState != Display.STATE_OFF;
+                mDisplayState =
+                        mDisplay == null ? Display.STATE_UNKNOWN : mDisplay.getCommittedState();
+                boolean displayVisible = Display.isOnState(mDisplayState) && !mIsScreenTurningOn;
+                boolean visible = mVisible && displayVisible;
                 if (DEBUG) {
                     Log.v(
                             TAG,
@@ -2486,6 +2506,20 @@
             }
         }
 
+        public void updateScreenTurningOn(boolean isScreenTurningOn) {
+            Message msg = mCaller.obtainMessageBO(MSG_UPDATE_SCREEN_TURNING_ON, isScreenTurningOn,
+                    null);
+            mCaller.sendMessage(msg);
+        }
+
+        public void onScreenTurningOn() throws RemoteException {
+            updateScreenTurningOn(true);
+        }
+
+        public void onScreenTurnedOn() throws RemoteException {
+            updateScreenTurningOn(false);
+        }
+
         @Override
         public void executeMessage(Message message) {
             switch (message.what) {
@@ -2530,6 +2564,13 @@
                             + ": " + message.arg1);
                     mEngine.doVisibilityChanged(message.arg1 != 0);
                     break;
+                case MSG_UPDATE_SCREEN_TURNING_ON:
+                    if (DEBUG) {
+                        Log.v(TAG,
+                                message.arg1 != 0 ? "Screen turning on" : "Screen turned on");
+                    }
+                    mEngine.onScreenTurningOnChanged(/* isScreenTurningOn= */ message.arg1 != 0);
+                    break;
                 case MSG_WALLPAPER_OFFSETS: {
                     mEngine.doOffsetsChanged(true);
                 } break;
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 9a93e1b..d06b0ce 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -354,6 +354,15 @@
     }
 
     /** @hide Just for debugging; not internationalized. */
+    public static void formatDuration(long time, long now, StringBuilder sb) {
+        if (time == 0) {
+            sb.append("--");
+            return;
+        }
+        formatDuration(time-now, sb, 0);
+    }
+
+    /** @hide Just for debugging; not internationalized. */
     public static void formatDuration(long time, long now, PrintWriter pw) {
         if (time == 0) {
             pw.print("--");
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 8c4e90c..5dd2d82 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -785,12 +785,13 @@
             DisplayEventReceiver.VsyncEventData vsyncEventData) {
         final long startNanos;
         final long frameIntervalNanos = vsyncEventData.frameInterval;
+        boolean resynced = false;
         try {
+            FrameTimeline timeline = mFrameData.update(frameTimeNanos, vsyncEventData);
             if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                Trace.traceBegin(Trace.TRACE_TAG_VIEW,
-                        "Choreographer#doFrame " + vsyncEventData.preferredFrameTimeline().vsyncId);
+                Trace.traceBegin(
+                        Trace.TRACE_TAG_VIEW, "Choreographer#doFrame " + timeline.mVsyncId);
             }
-            mFrameData.update(frameTimeNanos, vsyncEventData);
             synchronized (mLock) {
                 if (!mFrameScheduled) {
                     traceMessage("Frame not scheduled");
@@ -828,7 +829,9 @@
                                     + " ms in the past.");
                         }
                     }
-                    mFrameData.update(frameTimeNanos, mDisplayEventReceiver, jitterNanos);
+                    timeline = mFrameData.update(
+                            frameTimeNanos, mDisplayEventReceiver, jitterNanos);
+                    resynced = true;
                 }
 
                 if (frameTimeNanos < mLastFrameTimeNanos) {
@@ -860,6 +863,12 @@
                 mLastVsyncEventData = vsyncEventData;
             }
 
+            if (resynced && Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                String message = String.format("Choreographer#doFrame - resynced to %d in %.1fms",
+                        timeline.mVsyncId, (timeline.mDeadlineNanos - startNanos) * 0.000001f);
+                Trace.traceBegin(Trace.TRACE_TAG_VIEW, message);
+            }
+
             AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
 
             mFrameInfo.markInputHandlingStart();
@@ -875,6 +884,9 @@
             doCallbacks(Choreographer.CALLBACK_COMMIT, frameIntervalNanos);
         } finally {
             AnimationUtils.unlockAnimationClock();
+            if (resynced) {
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
         }
 
@@ -1149,7 +1161,8 @@
          * Update the frame data with a {@code DisplayEventReceiver.VsyncEventData} received from
          * native.
          */
-        void update(long frameTimeNanos, DisplayEventReceiver.VsyncEventData vsyncEventData) {
+        FrameTimeline update(
+                long frameTimeNanos, DisplayEventReceiver.VsyncEventData vsyncEventData) {
             if (vsyncEventData.frameTimelines.length != mFrameTimelines.length) {
                 throw new IllegalStateException(
                         "Length of native frame timelines received does not match Java. Did "
@@ -1164,6 +1177,7 @@
                 mFrameTimelines[i].update(frameTimeline.vsyncId,
                         frameTimeline.expectedPresentationTime, frameTimeline.deadline);
             }
+            return mFrameTimelines[mPreferredFrameTimelineIndex];
         }
 
         /**
@@ -1171,7 +1185,7 @@
          *
          * @param jitterNanos currentTime - frameTime
          */
-        void update(
+        FrameTimeline update(
                 long frameTimeNanos, DisplayEventReceiver displayEventReceiver, long jitterNanos) {
             int newPreferredIndex = 0;
             final long minimumDeadline =
@@ -1192,6 +1206,7 @@
             } else {
                 update(frameTimeNanos, newPreferredIndex);
             }
+            return mFrameTimelines[mPreferredFrameTimelineIndex];
         }
 
         void update(long frameTimeNanos, int newPreferredFrameTimelineIndex) {
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index cd89a56..e9984da 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -303,6 +303,7 @@
     /** @hide */
     public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d,
             @NonNull WindowlessWindowManager wwm, @NonNull String callsite) {
+        mSurfaceControl = wwm.mRootSurface;
         mWm = wwm;
         mViewRoot = new ViewRootImpl(c, d, mWm, new WindowlessWindowLayout());
         mCloseGuard.openWithCallSite("release", callsite);
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 1fac142..390503b 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -62,7 +62,7 @@
             in IAccessibilityInteractionConnection connection);
 
     void registerUiTestAutomationService(IBinder owner, IAccessibilityServiceClient client,
-        in AccessibilityServiceInfo info, int flags);
+        in AccessibilityServiceInfo info, int userId, int flags);
 
     void unregisterUiTestAutomationService(IAccessibilityServiceClient client);
 
diff --git a/core/java/android/window/BackMotionEvent.java b/core/java/android/window/BackMotionEvent.java
index 8012a1c..c475723 100644
--- a/core/java/android/window/BackMotionEvent.java
+++ b/core/java/android/window/BackMotionEvent.java
@@ -34,6 +34,8 @@
     private final float mTouchX;
     private final float mTouchY;
     private final float mProgress;
+    private final float mVelocityX;
+    private final float mVelocityY;
 
     @BackEvent.SwipeEdge
     private final int mSwipeEdge;
@@ -43,19 +45,32 @@
     /**
      * Creates a new {@link BackMotionEvent} instance.
      *
+     * <p>Note: Velocity is only computed for last event, for performance reasons.</p>
+     *
      * @param touchX Absolute X location of the touch point of this event.
      * @param touchY Absolute Y location of the touch point of this event.
      * @param progress Value between 0 and 1 on how far along the back gesture is.
+     * @param velocityX X velocity computed from the touch point of this event.
+     *                  Value in pixels/second. {@link Float#NaN} if was not computed.
+     * @param velocityY Y velocity computed from the touch point of this event.
+     *                  Value in pixels/second. {@link Float#NaN} if was not computed.
      * @param swipeEdge Indicates which edge the swipe starts from.
      * @param departingAnimationTarget The remote animation target of the departing
      *                                 application window.
      */
-    public BackMotionEvent(float touchX, float touchY, float progress,
+    public BackMotionEvent(
+            float touchX,
+            float touchY,
+            float progress,
+            float velocityX,
+            float velocityY,
             @BackEvent.SwipeEdge int swipeEdge,
             @Nullable RemoteAnimationTarget departingAnimationTarget) {
         mTouchX = touchX;
         mTouchY = touchY;
         mProgress = progress;
+        mVelocityX = velocityX;
+        mVelocityY = velocityY;
         mSwipeEdge = swipeEdge;
         mDepartingAnimationTarget = departingAnimationTarget;
     }
@@ -64,6 +79,8 @@
         mTouchX = in.readFloat();
         mTouchY = in.readFloat();
         mProgress = in.readFloat();
+        mVelocityX = in.readFloat();
+        mVelocityY = in.readFloat();
         mSwipeEdge = in.readInt();
         mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
     }
@@ -91,21 +108,13 @@
         dest.writeFloat(mTouchX);
         dest.writeFloat(mTouchY);
         dest.writeFloat(mProgress);
+        dest.writeFloat(mVelocityX);
+        dest.writeFloat(mVelocityY);
         dest.writeInt(mSwipeEdge);
         dest.writeTypedObject(mDepartingAnimationTarget, flags);
     }
 
     /**
-     * Returns the progress of a {@link BackEvent}.
-     *
-     * @see BackEvent#getProgress()
-     */
-    @FloatRange(from = 0, to = 1)
-    public float getProgress() {
-        return mProgress;
-    }
-
-    /**
      * Returns the absolute X location of the touch point.
      */
     public float getTouchX() {
@@ -120,6 +129,34 @@
     }
 
     /**
+     * Returns the progress of a {@link BackEvent}.
+     *
+     * @see BackEvent#getProgress()
+     */
+    @FloatRange(from = 0, to = 1)
+    public float getProgress() {
+        return mProgress;
+    }
+
+    /**
+     * Returns the X velocity computed from the touch point.
+     *
+     * @return value in pixels/second or {@link Float#NaN} if was not computed.
+     */
+    public float getVelocityX() {
+        return mVelocityX;
+    }
+
+    /**
+     * Returns the Y velocity computed from the touch point.
+     *
+     * @return value in pixels/second or {@link Float#NaN} if was not computed.
+     */
+    public float getVelocityY() {
+        return mVelocityY;
+    }
+
+    /**
      * Returns the screen edge that the swipe starts from.
      */
     @BackEvent.SwipeEdge
@@ -143,6 +180,8 @@
                 + "mTouchX=" + mTouchX
                 + ", mTouchY=" + mTouchY
                 + ", mProgress=" + mProgress
+                + ", mVelocityX=" + mVelocityX
+                + ", mVelocityY=" + mVelocityY
                 + ", mSwipeEdge" + mSwipeEdge
                 + ", mDepartingAnimationTarget" + mDepartingAnimationTarget
                 + "}";
diff --git a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
index ad0d1a4..3801188 100644
--- a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
+++ b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
@@ -20,6 +20,7 @@
 import android.service.voice.HotwordDetectedResult;
 import android.service.voice.HotwordDetectionServiceFailure;
 import android.service.voice.HotwordRejectedResult;
+import android.service.voice.SoundTriggerFailure;
 import android.service.voice.VisualQueryDetectionServiceFailure;
 
 /**
@@ -57,13 +58,6 @@
     void onRejected(in HotwordRejectedResult result);
 
     /**
-     * Called when the detection fails due to an error.
-     *
-     * @param status The error code that was seen.
-     */
-    void onError(int status);
-
-    /**
      * Called when the detection fails due to an error occurs in the
      * {@link HotwordDetectionService}.
      *
@@ -84,6 +78,15 @@
         in VisualQueryDetectionServiceFailure visualQueryDetectionServiceFailure);
 
     /**
+     * Called when the detection fails due to an error occurs in the
+     * {@link com.android.server.soundtrigger.SoundTriggerService}.
+     *
+     * @param soundTriggerFailure It provides the error code, error message and
+     *                                           suggested action.
+     */
+    void onSoundTriggerFailure(in SoundTriggerFailure soundTriggerFailure);
+
+    /**
      * Called when the detection fails due to an unknown error occurs.
      *
      * @param errorMessage It provides the error message.
diff --git a/core/java/com/android/internal/expresslog/Counter.java b/core/java/com/android/internal/expresslog/Counter.java
deleted file mode 100644
index 4a46d91..0000000
--- a/core/java/com/android/internal/expresslog/Counter.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.expresslog;
-
-import android.annotation.NonNull;
-
-import com.android.internal.util.FrameworkStatsLog;
-
-/** Counter encapsulates StatsD write API calls */
-public final class Counter {
-
-    // Not instantiable.
-    private Counter() {}
-
-    /**
-     * Increments Telemetry Express Counter metric by 1
-     * @param metricId to log, no-op if metricId is not defined in the TeX catalog
-     * @hide
-     */
-    public static void logIncrement(@NonNull String metricId) {
-        logIncrement(metricId, 1);
-    }
-
-    /**
-     * Increments Telemetry Express Counter metric by 1
-     * @param metricId to log, no-op if metricId is not defined in the TeX catalog
-     * @param uid used as a dimension for the count metric
-     * @hide
-     */
-    public static void logIncrementWithUid(@NonNull String metricId, int uid) {
-        logIncrementWithUid(metricId, uid, 1);
-    }
-
-    /**
-     * Increments Telemetry Express Counter metric by arbitrary value
-     * @param metricId to log, no-op if metricId is not defined in the TeX catalog
-     * @param amount to increment counter
-     * @hide
-     */
-    public static void logIncrement(@NonNull String metricId, long amount) {
-        final long metricIdHash = Utils.hashString(metricId);
-        FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_EVENT_REPORTED, metricIdHash, amount);
-    }
-
-    /**
-     * Increments Telemetry Express Counter metric by arbitrary value
-     * @param metricId to log, no-op if metricId is not defined in the TeX catalog
-     * @param uid used as a dimension for the count metric
-     * @param amount to increment counter
-     * @hide
-     */
-    public static void logIncrementWithUid(@NonNull String metricId, int uid, long amount) {
-        final long metricIdHash = Utils.hashString(metricId);
-        FrameworkStatsLog.write(
-                FrameworkStatsLog.EXPRESS_UID_EVENT_REPORTED, metricIdHash, amount, uid);
-    }
-}
diff --git a/core/java/com/android/internal/expresslog/Histogram.java b/core/java/com/android/internal/expresslog/Histogram.java
deleted file mode 100644
index 2fe784a..0000000
--- a/core/java/com/android/internal/expresslog/Histogram.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.expresslog;
-
-import android.annotation.FloatRange;
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-
-import com.android.internal.util.FrameworkStatsLog;
-
-import java.util.Arrays;
-
-/** Histogram encapsulates StatsD write API calls */
-public final class Histogram {
-
-    private final long mMetricIdHash;
-    private final BinOptions mBinOptions;
-
-    /**
-     * Creates Histogram metric logging wrapper
-     *
-     * @param metricId   to log, logging will be no-op if metricId is not defined in the TeX catalog
-     * @param binOptions to calculate bin index for samples
-     * @hide
-     */
-    public Histogram(@NonNull String metricId, @NonNull BinOptions binOptions) {
-        mMetricIdHash = Utils.hashString(metricId);
-        mBinOptions = binOptions;
-    }
-
-    /**
-     * Logs increment sample count for automatically calculated bin
-     *
-     * @param sample value
-     * @hide
-     */
-    public void logSample(float sample) {
-        final int binIndex = mBinOptions.getBinForSample(sample);
-        FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_HISTOGRAM_SAMPLE_REPORTED, mMetricIdHash,
-                /*count*/ 1, binIndex);
-    }
-
-    /**
-     * Logs increment sample count for automatically calculated bin
-     *
-     * @param uid used as a dimension for the count metric
-     * @param sample value
-     * @hide
-     */
-    public void logSampleWithUid(int uid, float sample) {
-        final int binIndex = mBinOptions.getBinForSample(sample);
-        FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED,
-                mMetricIdHash, /*count*/ 1, binIndex, uid);
-    }
-
-    /** Used by Histogram to map data sample to corresponding bin */
-    public interface BinOptions {
-        /**
-         * Returns bins count to be used by a histogram
-         *
-         * @return bins count used to initialize Options, including overflow & underflow bins
-         * @hide
-         */
-        int getBinsCount();
-
-        /**
-         * Returns bin index for the input sample value
-         * index == 0 stands for underflow
-         * index == getBinsCount() - 1 stands for overflow
-         *
-         * @return zero based index
-         * @hide
-         */
-        int getBinForSample(float sample);
-    }
-
-    /** Used by Histogram to map data sample to corresponding bin for uniform bins */
-    public static final class UniformOptions implements BinOptions {
-
-        private final int mBinCount;
-        private final float mMinValue;
-        private final float mExclusiveMaxValue;
-        private final float mBinSize;
-
-        /**
-         * Creates options for uniform (linear) sized bins
-         *
-         * @param binCount          amount of histogram bins. 2 bin indexes will be calculated
-         *                          automatically to represent underflow & overflow bins
-         * @param minValue          is included in the first bin, values less than minValue
-         *                          go to underflow bin
-         * @param exclusiveMaxValue is included in the overflow bucket. For accurate
-         *                          measure up to kMax, then exclusiveMaxValue
-         *                          should be set to kMax + 1
-         * @hide
-         */
-        public UniformOptions(@IntRange(from = 1) int binCount, float minValue,
-                float exclusiveMaxValue) {
-            if (binCount < 1) {
-                throw new IllegalArgumentException("Bin count should be positive number");
-            }
-
-            if (exclusiveMaxValue <= minValue) {
-                throw new IllegalArgumentException("Bins range invalid (maxValue < minValue)");
-            }
-
-            mMinValue = minValue;
-            mExclusiveMaxValue = exclusiveMaxValue;
-            mBinSize = (mExclusiveMaxValue - minValue) / binCount;
-
-            // Implicitly add 2 for the extra underflow & overflow bins
-            mBinCount = binCount + 2;
-        }
-
-        @Override
-        public int getBinsCount() {
-            return mBinCount;
-        }
-
-        @Override
-        public int getBinForSample(float sample) {
-            if (sample < mMinValue) {
-                // goes to underflow
-                return 0;
-            } else if (sample >= mExclusiveMaxValue) {
-                // goes to overflow
-                return mBinCount - 1;
-            }
-            return (int) ((sample - mMinValue) / mBinSize + 1);
-        }
-    }
-
-    /** Used by Histogram to map data sample to corresponding bin for scaled bins */
-    public static final class ScaledRangeOptions implements BinOptions {
-        // store minimum value per bin
-        final long[] mBins;
-
-        /**
-         * Creates options for scaled range bins
-         *
-         * @param binCount      amount of histogram bins. 2 bin indexes will be calculated
-         *                      automatically to represent underflow & overflow bins
-         * @param minValue      is included in the first bin, values less than minValue
-         *                      go to underflow bin
-         * @param firstBinWidth used to represent first bin width and as a reference to calculate
-         *                      width for consecutive bins
-         * @param scaleFactor   used to calculate width for consecutive bins
-         * @hide
-         */
-        public ScaledRangeOptions(@IntRange(from = 1) int binCount, int minValue,
-                @FloatRange(from = 1.f) float firstBinWidth,
-                @FloatRange(from = 1.f) float scaleFactor) {
-            if (binCount < 1) {
-                throw new IllegalArgumentException("Bin count should be positive number");
-            }
-
-            if (firstBinWidth < 1.f) {
-                throw new IllegalArgumentException(
-                        "First bin width invalid (should be 1.f at minimum)");
-            }
-
-            if (scaleFactor < 1.f) {
-                throw new IllegalArgumentException(
-                        "Scaled factor invalid (should be 1.f at minimum)");
-            }
-
-            // precalculating bins ranges (no need to create a bin for underflow reference value)
-            mBins = initBins(binCount + 1, minValue, firstBinWidth, scaleFactor);
-        }
-
-        @Override
-        public int getBinsCount() {
-            return mBins.length + 1;
-        }
-
-        @Override
-        public int getBinForSample(float sample) {
-            if (sample < mBins[0]) {
-                // goes to underflow
-                return 0;
-            } else if (sample >= mBins[mBins.length - 1]) {
-                // goes to overflow
-                return mBins.length;
-            }
-
-            return lower_bound(mBins, (long) sample) + 1;
-        }
-
-        // To find lower bound using binary search implementation of Arrays utility class
-        private static int lower_bound(long[] array, long sample) {
-            int index = Arrays.binarySearch(array, sample);
-            // If key is not present in the array
-            if (index < 0) {
-                // Index specify the position of the key when inserted in the sorted array
-                // so the element currently present at this position will be the lower bound
-                return Math.abs(index) - 2;
-            }
-            return index;
-        }
-
-        private static long[] initBins(int count, int minValue, float firstBinWidth,
-                float scaleFactor) {
-            long[] bins = new long[count];
-            bins[0] = minValue;
-            double lastWidth = firstBinWidth;
-            for (int i = 1; i < count; i++) {
-                // current bin minValue = previous bin width * scaleFactor
-                double currentBinMinValue = bins[i - 1] + lastWidth;
-                if (currentBinMinValue > Integer.MAX_VALUE) {
-                    throw new IllegalArgumentException(
-                        "Attempted to create a bucket larger than maxint");
-                }
-
-                bins[i] = (long) currentBinMinValue;
-                lastWidth *= scaleFactor;
-            }
-            return bins;
-        }
-    }
-}
diff --git a/core/java/com/android/internal/expresslog/OWNERS b/core/java/com/android/internal/expresslog/OWNERS
deleted file mode 100644
index ee865b1..0000000
--- a/core/java/com/android/internal/expresslog/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/stats/OWNERS
diff --git a/core/java/com/android/internal/expresslog/TEST_MAPPING b/core/java/com/android/internal/expresslog/TEST_MAPPING
deleted file mode 100644
index c9b0cf8..0000000
--- a/core/java/com/android/internal/expresslog/TEST_MAPPING
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "ExpressLogTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
-    }
-  ]
-}
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
index e2c096c1..aa6b1c0 100644
--- a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
+++ b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
@@ -20,7 +20,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.expresslog.Counter;
+import com.android.modules.expresslog.Counter;
 
 import java.io.IOException;
 import java.util.Arrays;
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index fc26766..6bec6bc 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -225,7 +225,6 @@
                 "android_security_Scrypt.cpp",
                 "com_android_internal_content_om_OverlayConfig.cpp",
                 "com_android_internal_content_om_OverlayManagerImpl.cpp",
-                "com_android_internal_expresslog_Utils.cpp",
                 "com_android_internal_net_NetworkUtilsInternal.cpp",
                 "com_android_internal_os_ClassLoaderFactory.cpp",
                 "com_android_internal_os_FuseAppLoop.cpp",
@@ -262,6 +261,7 @@
                 "libstatssocket_lazy",
                 "libskia",
                 "libtextclassifier_hash_static",
+                "libexpresslog_jni",
             ],
 
             shared_libs: [
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b550f28..21bdf09 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -200,7 +200,7 @@
 extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
 extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env);
 extern int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env);
-extern int register_com_android_internal_expresslog_Utils(JNIEnv* env);
+extern int register_com_android_modules_expresslog_Utils(JNIEnv* env);
 extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env);
 extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
 extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
@@ -1586,7 +1586,7 @@
         REG_JNI(register_android_os_incremental_IncrementalManager),
         REG_JNI(register_com_android_internal_content_om_OverlayConfig),
         REG_JNI(register_com_android_internal_content_om_OverlayManagerImpl),
-        REG_JNI(register_com_android_internal_expresslog_Utils),
+        REG_JNI(register_com_android_modules_expresslog_Utils),
         REG_JNI(register_com_android_internal_net_NetworkUtilsInternal),
         REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
         REG_JNI(register_com_android_internal_os_LongArrayMultiStateCounter),
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index bce53328..4e4abec 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -104,6 +104,3 @@
 
 # PM
 per-file com_android_internal_content_* = file:/PACKAGE_MANAGER_OWNERS
-
-# Stats/expresslog
-per-file *expresslog* = file:/services/core/java/com/android/server/stats/OWNERS
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 8ba4eed..e1be0cd 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2481,36 +2481,31 @@
 
     if (jSurroundFormats == nullptr) {
         ALOGE("jSurroundFormats is NULL");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return static_cast<jint>(AUDIO_JAVA_BAD_VALUE);
     }
     if (!env->IsInstanceOf(jSurroundFormats, gMapClass)) {
         ALOGE("getSurroundFormats not a map");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return static_cast<jint>(AUDIO_JAVA_BAD_VALUE);
     }
 
     jint jStatus;
     unsigned int numSurroundFormats = 0;
-    audio_format_t *surroundFormats = nullptr;
-    bool *surroundFormatsEnabled = nullptr;
-    status_t status = AudioSystem::getSurroundFormats(&numSurroundFormats, surroundFormats,
-                                                      surroundFormatsEnabled);
+    status_t status = AudioSystem::getSurroundFormats(&numSurroundFormats, nullptr, nullptr);
     if (status != NO_ERROR) {
         ALOGE_IF(status != NO_ERROR, "AudioSystem::getSurroundFormats error %d", status);
-        jStatus = nativeToJavaStatus(status);
-        goto exit;
+        return nativeToJavaStatus(status);
     }
     if (numSurroundFormats == 0) {
-        jStatus = (jint)AUDIO_JAVA_SUCCESS;
-        goto exit;
+        return static_cast<jint>(AUDIO_JAVA_SUCCESS);
     }
-    surroundFormats = (audio_format_t *)calloc(numSurroundFormats, sizeof(audio_format_t));
-    surroundFormatsEnabled = (bool *)calloc(numSurroundFormats, sizeof(bool));
-    status = AudioSystem::getSurroundFormats(&numSurroundFormats, surroundFormats,
-                                             surroundFormatsEnabled);
+    auto surroundFormats = std::make_unique<audio_format_t[]>(numSurroundFormats);
+    auto surroundFormatsEnabled = std::make_unique<bool[]>(numSurroundFormats);
+    status = AudioSystem::getSurroundFormats(&numSurroundFormats, &surroundFormats[0],
+                                             &surroundFormatsEnabled[0]);
     jStatus = nativeToJavaStatus(status);
     if (status != NO_ERROR) {
         ALOGE_IF(status != NO_ERROR, "AudioSystem::getSurroundFormats error %d", status);
-        goto exit;
+        return jStatus;
     }
     for (size_t i = 0; i < numSurroundFormats; i++) {
         int audioFormat = audioFormatFromNative(surroundFormats[i]);
@@ -2526,9 +2521,6 @@
         env->DeleteLocalRef(enabled);
     }
 
-exit:
-    free(surroundFormats);
-    free(surroundFormatsEnabled);
     return jStatus;
 }
 
@@ -2538,31 +2530,28 @@
 
     if (jSurroundFormats == nullptr) {
         ALOGE("jSurroundFormats is NULL");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return static_cast<jint>(AUDIO_JAVA_BAD_VALUE);
     }
     if (!env->IsInstanceOf(jSurroundFormats, gArrayListClass)) {
         ALOGE("jSurroundFormats not an arraylist");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return static_cast<jint>(AUDIO_JAVA_BAD_VALUE);
     }
     jint jStatus;
     unsigned int numSurroundFormats = 0;
-    audio_format_t *surroundFormats = nullptr;
-    status_t status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, surroundFormats);
+    status_t status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, nullptr);
     if (status != NO_ERROR) {
         ALOGE_IF(status != NO_ERROR, "AudioSystem::getReportedSurroundFormats error %d", status);
-        jStatus = nativeToJavaStatus(status);
-        goto exit;
+        return nativeToJavaStatus(status);
     }
     if (numSurroundFormats == 0) {
-        jStatus = (jint)AUDIO_JAVA_SUCCESS;
-        goto exit;
+        return static_cast<jint>(AUDIO_JAVA_SUCCESS);
     }
-    surroundFormats = (audio_format_t *)calloc(numSurroundFormats, sizeof(audio_format_t));
-    status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, surroundFormats);
+    auto surroundFormats = std::make_unique<audio_format_t[]>(numSurroundFormats);
+    status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, &surroundFormats[0]);
     jStatus = nativeToJavaStatus(status);
     if (status != NO_ERROR) {
         ALOGE_IF(status != NO_ERROR, "AudioSystem::getReportedSurroundFormats error %d", status);
-        goto exit;
+        return jStatus;
     }
     for (size_t i = 0; i < numSurroundFormats; i++) {
         int audioFormat = audioFormatFromNative(surroundFormats[i]);
@@ -2576,8 +2565,6 @@
         env->DeleteLocalRef(surroundFormat);
     }
 
-exit:
-    free(surroundFormats);
     return jStatus;
 }
 
diff --git a/core/jni/com_android_internal_expresslog_Utils.cpp b/core/jni/com_android_internal_expresslog_Utils.cpp
deleted file mode 100644
index d33a7bd..0000000
--- a/core/jni/com_android_internal_expresslog_Utils.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 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.
- */
-
-#include <nativehelper/JNIHelp.h>
-#include <utils/hash/farmhash.h>
-
-#include "core_jni_helpers.h"
-
-// ----------------------------------------------------------------------------
-// JNI Glue
-// ----------------------------------------------------------------------------
-
-static jclass g_stringClass = nullptr;
-
-/**
- * Class:     com_android_internal_expresslog_Utils
- * Method:    hashString
- * Signature: (Ljava/lang/String;)J
- */
-static jlong hashString(JNIEnv* env, jclass /*class*/, jstring metricNameObj) {
-    ScopedUtfChars name(env, metricNameObj);
-    if (name.c_str() == nullptr) {
-        return 0;
-    }
-
-    return static_cast<jlong>(farmhash::Fingerprint64(name.c_str(), name.size()));
-}
-
-static const JNINativeMethod g_methods[] = {
-        {"hashString", "(Ljava/lang/String;)J", (void*)hashString},
-};
-
-static const char* const kUtilsPathName = "com/android/internal/expresslog/Utils";
-
-namespace android {
-
-int register_com_android_internal_expresslog_Utils(JNIEnv* env) {
-    jclass stringClass = FindClassOrDie(env, "java/lang/String");
-    g_stringClass = MakeGlobalRefOrDie(env, stringClass);
-
-    return RegisterMethodsOrDie(env, kUtilsPathName, g_methods, NELEM(g_methods));
-}
-
-} // namespace android
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 78d3923..05b38a5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1746,37 +1746,6 @@
         android:protectionLevel="dangerous"
         android:permissionFlags="hardRestricted" />
 
-    <!-- @TestApi Allows an application to access wrist temperature data from the watch sensors.
-        <p class="note"><strong>Note: </strong> This permission is for Wear OS only.
-        <p>Protection level: dangerous
-        @hide
-        -->
-    <permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE"
-                android:permissionGroup="android.permission-group.UNDEFINED"
-                android:label="@string/permlab_bodySensorsWristTemperature"
-                android:description="@string/permdesc_bodySensorsWristTemperature"
-                android:backgroundPermission="android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND"
-                android:protectionLevel="dangerous" />
-
-    <!-- @TestApi Allows an application to access wrist temperature data from the watch sensors.
-         If you're requesting this permission, you must also request
-         {@link #BODY_SENSORS_WRIST_TEMPERATURE}. Requesting this permission by itself doesn't
-         give you wrist temperature body sensors access.
-         <p class="note"><strong>Note: </strong> This permission is for Wear OS only.
-         <p>Protection level: dangerous
-
-         <p> This is a hard restricted permission which cannot be held by an app until
-         the installer on record allowlists the permission. For more details see
-         {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
-         @hide
-    -->
-    <permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND"
-                android:permissionGroup="android.permission-group.UNDEFINED"
-                android:label="@string/permlab_bodySensors_wristTemperature_background"
-                android:description="@string/permdesc_bodySensors_wristTemperature_background"
-                android:protectionLevel="dangerous"
-                android:permissionFlags="hardRestricted" />
-
     <!-- Allows an app to use fingerprint hardware.
          <p>Protection level: normal
          @deprecated Applications should request {@link
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e4d74b5..9f10ae60 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1373,6 +1373,9 @@
     <!-- Number of notifications to keep in the notification service historical archive -->
     <integer name="config_notificationServiceArchiveSize">100</integer>
 
+    <!-- List of packages that will be able to use full screen intent in notifications by default -->
+    <string-array name="config_useFullScreenIntentPackages" translatable="false" />
+
     <!-- Allow the menu hard key to be disabled in LockScreen on some devices -->
     <bool name="config_disableMenuKeyInLockScreen">false</bool>
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 8231407..947dc2d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1347,16 +1347,6 @@
     <!-- Description of the background body sensors permission, listed so the user can decide whether to allow the application to access data from body sensors in the background. [CHAR LIMIT=NONE] -->
     <string name="permdesc_bodySensors_background" product="default">Allows the app to access body sensor data, such as heart rate, temperature, and blood oxygen percentage, while the app is in the background.</string>
 
-    <!-- Title of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access body sensor wrist temperature data. [CHAR LIMIT=NONE] -->
-    <string name="permlab_bodySensorsWristTemperature">Access body sensor wrist temperature data while the app is in use.</string>
-    <!-- Description of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access data from body sensors. [CHAR LIMIT=NONE] -->
-    <string name="permdesc_bodySensorsWristTemperature" product="default">Allows the app to access body sensor wrist temperature data, while the app is in use.</string>
-
-    <!-- Title of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access body sensor wrist temperature data. [CHAR LIMIT=NONE] -->
-    <string name="permlab_bodySensors_wristTemperature_background">Access body sensor wrist temperature data while the app is in the background.</string>
-    <!-- Description of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access data from body sensors. [CHAR LIMIT=NONE] -->
-    <string name="permdesc_bodySensors_wristTemperature_background" product="default">Allows the app to access body sensor wrist temperature data, while the app is in the background.</string>
-
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_readCalendar">Read calendar events and details</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c6c1c8f..a823d1f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2017,6 +2017,7 @@
   <java-symbol type="integer" name="config_notificationsBatteryMediumARGB" />
   <java-symbol type="integer" name="config_notificationsBatteryNearlyFullLevel" />
   <java-symbol type="integer" name="config_notificationServiceArchiveSize" />
+  <java-symbol type="array" name="config_useFullScreenIntentPackages" />
   <java-symbol type="integer" name="config_previousVibrationsDumpLimit" />
   <java-symbol type="integer" name="config_defaultVibrationAmplitude" />
   <java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" />
diff --git a/core/tests/coretests/src/android/app/UiAutomationTest.java b/core/tests/coretests/src/android/app/UiAutomationTest.java
new file mode 100644
index 0000000..3ac5057
--- /dev/null
+++ b/core/tests/coretests/src/android/app/UiAutomationTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Looper;
+import android.os.UserManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class UiAutomationTest {
+
+    private static final int SECONDARY_DISPLAY_ID = 42;
+    private static final int DISPLAY_ID_ASSIGNED_TO_USER = 108;
+
+    private final Looper mLooper = Looper.getMainLooper();
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private IUiAutomationConnection mConnection;
+
+    @Before
+    public void setFixtures() {
+        when(mContext.getMainLooper()).thenReturn(mLooper);
+        mockSystemService(UserManager.class, mUserManager);
+
+        // Set default expectations
+        mockVisibleBackgroundUsersSupported(/* supported= */ false);
+        mockUserVisibility(/* visible= */ true);
+        // make sure it's not used, unless explicitly mocked
+        mockDisplayAssignedToUser(INVALID_DISPLAY);
+        mockContextDisplay(DEFAULT_DISPLAY);
+    }
+
+    @Test
+    public void testContextConstructor_nullContext() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new UiAutomation((Context) null, mConnection));
+    }
+
+    @Test
+    public void testContextConstructor_nullConnection() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new UiAutomation(mContext, (IUiAutomationConnection) null));
+    }
+
+    @Test
+    public void testGetDisplay_contextWithSecondaryDisplayId() {
+        mockContextDisplay(SECONDARY_DISPLAY_ID);
+
+        UiAutomation uiAutomation = new UiAutomation(mContext, mConnection);
+
+        // It's always DEFAULT_DISPLAY regardless, unless the device supports visible bg users
+        assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId())
+                .isEqualTo(DEFAULT_DISPLAY);
+    }
+
+    @Test
+    public void testGetDisplay_contextWithInvalidDisplayId() {
+        mockContextDisplay(INVALID_DISPLAY);
+
+        UiAutomation uiAutomation = new UiAutomation(mContext, mConnection);
+
+        assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId())
+                .isEqualTo(DEFAULT_DISPLAY);
+    }
+
+    @Test
+    public void testGetDisplay_visibleBgUsers() {
+        mockVisibleBackgroundUsersSupported(/* supported= */ true);
+        mockContextDisplay(SECONDARY_DISPLAY_ID);
+        // Should be using display from context, not from user
+        mockDisplayAssignedToUser(DISPLAY_ID_ASSIGNED_TO_USER);
+
+        UiAutomation uiAutomation = new UiAutomation(mContext, mConnection);
+
+        assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId())
+                .isEqualTo(SECONDARY_DISPLAY_ID);
+    }
+
+    @Test
+    public void testGetDisplay_visibleBgUsers_contextWithInvalidDisplayId() {
+        mockVisibleBackgroundUsersSupported(/* supported= */ true);
+        mockContextDisplay(INVALID_DISPLAY);
+        mockDisplayAssignedToUser(DISPLAY_ID_ASSIGNED_TO_USER);
+
+        UiAutomation uiAutomation = new UiAutomation(mContext, mConnection);
+
+        assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId())
+                .isEqualTo(DISPLAY_ID_ASSIGNED_TO_USER);
+    }
+
+    private <T> void mockSystemService(Class<T> svcClass, T svc) {
+        String svcName = svcClass.getName();
+        when(mContext.getSystemServiceName(svcClass)).thenReturn(svcName);
+        when(mContext.getSystemService(svcName)).thenReturn(svc);
+    }
+
+    private void mockVisibleBackgroundUsersSupported(boolean supported) {
+        when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(supported);
+    }
+
+    private void mockContextDisplay(int displayId) {
+        when(mContext.getDisplayId()).thenReturn(displayId);
+    }
+
+    private void mockDisplayAssignedToUser(int displayId) {
+        when(mUserManager.getMainDisplayIdAssignedToUser()).thenReturn(displayId);
+    }
+
+    private void mockUserVisibility(boolean visible) {
+        when(mUserManager.isUserVisible()).thenReturn(visible);
+    }
+}
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 3b6e8ea..cde100c 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -70,7 +70,13 @@
     private ApplicationInfo mApplicationInfo;
 
     private final BackMotionEvent mBackEvent = new BackMotionEvent(
-            0, 0, 0, BackEvent.EDGE_LEFT, null);
+            /* touchX = */ 0,
+            /* touchY = */ 0,
+            /* progress = */ 0,
+            /* velocityX = */ 0,
+            /* velocityY = */ 0,
+            /* swipeEdge = */ BackEvent.EDGE_LEFT,
+            /* departingAnimationTarget = */ null);
 
     @Before
     public void setUp() throws Exception {
diff --git a/core/tests/expresslog/AndroidManifest.xml b/core/tests/expresslog/AndroidManifest.xml
deleted file mode 100644
index 94a39e0..0000000
--- a/core/tests/expresslog/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2023 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          android:installLocation="internalOnly"
-          package="com.android.internal.expresslog" >
-
-    <application >
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.internal.expresslog"
-            android:label="Telemetry Express Logging Helper Tests" />
-
-</manifest>
diff --git a/core/tests/expresslog/OWNERS b/core/tests/expresslog/OWNERS
deleted file mode 100644
index 3dc958b..0000000
--- a/core/tests/expresslog/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-# Bug component: 719316
-# Stats/expresslog
-file:/services/core/java/com/android/server/stats/OWNERS
diff --git a/core/tests/expresslog/TEST_MAPPING b/core/tests/expresslog/TEST_MAPPING
deleted file mode 100644
index c9b0cf8..0000000
--- a/core/tests/expresslog/TEST_MAPPING
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "ExpressLogTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
-    }
-  ]
-}
\ No newline at end of file
diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java
deleted file mode 100644
index ee62d75..0000000
--- a/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.internal.expresslog;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-@SmallTest
-public class ScaledRangeOptionsTest {
-    private static final String TAG = ScaledRangeOptionsTest.class.getSimpleName();
-
-    @Test
-    public void testGetBinsCount() {
-        Histogram.ScaledRangeOptions options1 = new Histogram.ScaledRangeOptions(1, 100, 100, 2);
-        assertEquals(3, options1.getBinsCount());
-
-        Histogram.ScaledRangeOptions options10 = new Histogram.ScaledRangeOptions(10, 100, 100, 2);
-        assertEquals(12, options10.getBinsCount());
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructZeroBinsCount() {
-        new Histogram.ScaledRangeOptions(0, 100, 100, 2);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructNegativeBinsCount() {
-        new Histogram.ScaledRangeOptions(-1, 100, 100, 2);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructNegativeFirstBinWidth() {
-        new Histogram.ScaledRangeOptions(10, 100, -100, 2);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructTooSmallFirstBinWidth() {
-        new Histogram.ScaledRangeOptions(10, 100, 0.5f, 2);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructNegativeScaleFactor() {
-        new Histogram.ScaledRangeOptions(10, 100, 100, -2);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructTooSmallScaleFactor() {
-        new Histogram.ScaledRangeOptions(10, 100, 100, 0.5f);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructTooBigScaleFactor() {
-        new Histogram.ScaledRangeOptions(10, 100, 100, 500.f);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructTooBigBinRange() {
-        new Histogram.ScaledRangeOptions(100, 100, 100, 10.f);
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual1() {
-        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 1, 1);
-        assertEquals(12, options.getBinsCount());
-
-        assertEquals(11, options.getBinForSample(11));
-
-        for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
-            assertEquals(i, options.getBinForSample(i));
-        }
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual2() {
-        // this should produce bin otpions similar to linear histogram with bin width 2
-        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 2, 1);
-        assertEquals(12, options.getBinsCount());
-
-        for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
-            assertEquals(i, options.getBinForSample(i * 2));
-            assertEquals(i, options.getBinForSample(i * 2 - 1));
-        }
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual5() {
-        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(2, 0, 5, 1);
-        assertEquals(4, options.getBinsCount());
-        for (int i = 0; i < 2; i++) {
-            for (int sample = 0; sample < 5; sample++) {
-                assertEquals(i + 1, options.getBinForSample(i * 5 + sample));
-            }
-        }
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual10() {
-        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 10, 1);
-        assertEquals(0, options.getBinForSample(0));
-        assertEquals(options.getBinsCount() - 2, options.getBinForSample(100));
-        assertEquals(options.getBinsCount() - 1, options.getBinForSample(101));
-
-        final float binSize = (101 - 1) / 10f;
-        for (int i = 1, bins = options.getBinsCount() - 1; i < bins; i++) {
-            assertEquals(i, options.getBinForSample(i * binSize));
-        }
-    }
-
-    @Test
-    public void testBinIndexForScaleFactor2() {
-        final int binsCount = 10;
-        final int minValue = 10;
-        final int firstBinWidth = 5;
-        final int scaledFactor = 2;
-
-        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(
-                binsCount, minValue, firstBinWidth, scaledFactor);
-        assertEquals(binsCount + 2, options.getBinsCount());
-        long[] binCounts = new long[10];
-
-        // precalculate max valid value - start value for the overflow bin
-        int lastBinStartValue = minValue; //firstBinMin value
-        int lastBinWidth = firstBinWidth;
-        for (int binIdx = 2; binIdx <= binsCount + 1; binIdx++) {
-            lastBinStartValue = lastBinStartValue + lastBinWidth;
-            lastBinWidth *= scaledFactor;
-        }
-
-        // underflow bin
-        for (int i = 1; i < minValue; i++) {
-            assertEquals(0, options.getBinForSample(i));
-        }
-
-        for (int i = 10; i < lastBinStartValue; i++) {
-            assertTrue(options.getBinForSample(i) > 0);
-            assertTrue(options.getBinForSample(i) <= binsCount);
-            binCounts[options.getBinForSample(i) - 1]++;
-        }
-
-        // overflow bin
-        assertEquals(binsCount + 1, options.getBinForSample(lastBinStartValue));
-
-        for (int i = 1; i < binsCount; i++) {
-            assertEquals(binCounts[i], binCounts[i - 1] * 2L);
-        }
-    }
-}
diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
deleted file mode 100644
index 037dbb3..0000000
--- a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.internal.expresslog;
-
-import androidx.test.filters.SmallTest;
-
-import static org.junit.Assert.assertEquals;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-@SmallTest
-public class UniformOptionsTest {
-    private static final String TAG = UniformOptionsTest.class.getSimpleName();
-
-    @Test
-    public void testGetBinsCount() {
-        Histogram.UniformOptions options1 = new Histogram.UniformOptions(1, 100, 1000);
-        assertEquals(3, options1.getBinsCount());
-
-        Histogram.UniformOptions options10 = new Histogram.UniformOptions(10, 100, 1000);
-        assertEquals(12, options10.getBinsCount());
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructZeroBinsCount() {
-        new Histogram.UniformOptions(0, 100, 1000);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructNegativeBinsCount() {
-        new Histogram.UniformOptions(-1, 100, 1000);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructMaxValueLessThanMinValue() {
-        new Histogram.UniformOptions(10, 1000, 100);
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual1() {
-        Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 11);
-        for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
-            assertEquals(i, options.getBinForSample(i));
-        }
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual2() {
-        Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 21);
-        for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
-            assertEquals(i, options.getBinForSample(i * 2));
-            assertEquals(i, options.getBinForSample(i * 2 - 1));
-        }
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual5() {
-        Histogram.UniformOptions options = new Histogram.UniformOptions(2, 0, 10);
-        assertEquals(4, options.getBinsCount());
-        for (int i = 0; i < 2; i++) {
-            for (int sample = 0; sample < 5; sample++) {
-                assertEquals(i + 1, options.getBinForSample(i * 5 + sample));
-            }
-        }
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual10() {
-        Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 101);
-        assertEquals(0, options.getBinForSample(0));
-        assertEquals(options.getBinsCount() - 2, options.getBinForSample(100));
-        assertEquals(options.getBinsCount() - 1, options.getBinForSample(101));
-
-        final float binSize = (101 - 1) / 10f;
-        for (int i = 1, bins = options.getBinsCount() - 1; i < bins; i++) {
-            assertEquals(i, options.getBinForSample(i * binSize));
-        }
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual90() {
-        final int binCount = 10;
-        final int minValue = 100;
-        final int maxValue = 100000;
-
-        Histogram.UniformOptions options = new Histogram.UniformOptions(binCount, minValue,
-                maxValue);
-
-        // logging underflow sample
-        assertEquals(0, options.getBinForSample(minValue - 1));
-
-        // logging overflow sample
-        assertEquals(binCount + 1, options.getBinForSample(maxValue));
-        assertEquals(binCount + 1, options.getBinForSample(maxValue + 1));
-
-        // logging min edge sample
-        assertEquals(1, options.getBinForSample(minValue));
-
-        // logging max edge sample
-        assertEquals(binCount, options.getBinForSample(maxValue - 1));
-
-        // logging single valid sample per bin
-        final int binSize = (maxValue - minValue) / binCount;
-
-        for (int i = 0; i < binCount; i++) {
-            assertEquals(i + 1, options.getBinForSample(minValue + binSize * i));
-        }
-    }
-}
diff --git a/data/etc/preinstalled-packages-platform-overlays.xml b/data/etc/preinstalled-packages-platform-overlays.xml
index 9959433..2fd65dc 100644
--- a/data/etc/preinstalled-packages-platform-overlays.xml
+++ b/data/etc/preinstalled-packages-platform-overlays.xml
@@ -56,6 +56,9 @@
     <install-in-user-type package="com.android.internal.systemui.navbar.transparent">
         <install-in user-type="FULL" />
     </install-in-user-type>
+    <install-in-user-type package="com.android.role.notes.enabled">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
     <install-in-user-type package="com.android.theme.color.amethyst">
         <install-in user-type="FULL" />
         <install-in user-type="PROFILE" />
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 0b29973..56c3068 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -2083,32 +2083,29 @@
             }
 
             sIsP010SupportedForAV1Initialized = true;
-
-            if (hasHardwareDecoder("video/av01")) {
-                sIsP010SupportedForAV1 = true;
-                return true;
-            }
-
-            sIsP010SupportedForAV1 = Build.VERSION.DEVICE_INITIAL_SDK_INT
-                    >= Build.VERSION_CODES.S;
-            return sIsP010SupportedForAV1;
+            return sIsP010SupportedForAV1 = isP010SupportedforMime("video/av01");
         }
     }
 
     /**
-     * Checks if the device has hardware decoder for the target mime type.
+     * Checks if the device supports decoding 10-bit for the given mime type.
      */
-    private static boolean hasHardwareDecoder(String mime) {
-        final MediaCodecList sMCL = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        for (MediaCodecInfo info : sMCL.getCodecInfos()) {
-            if (info.isEncoder() == false && info.isHardwareAccelerated()) {
-                try {
-                     if (info.getCapabilitiesForType(mime) != null) {
-                         return true;
-                     }
-                } catch (IllegalArgumentException e) {
-                     // mime is not supported
-                     return false;
+    private static boolean isP010SupportedforMime(String mime) {
+        MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        for (MediaCodecInfo mediaCodecInfo : codecList.getCodecInfos()) {
+            if (mediaCodecInfo.isEncoder()) {
+                continue;
+            }
+            for (String mediaType : mediaCodecInfo.getSupportedTypes()) {
+                if (mediaType.equalsIgnoreCase(mime)) {
+                    MediaCodecInfo.CodecCapabilities codecCapabilities =
+                        mediaCodecInfo.getCapabilitiesForType(mediaType);
+                    for (int i = 0; i < codecCapabilities.colorFormats.length; ++i) {
+                        if (codecCapabilities.colorFormats[i]
+                            == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010) {
+                            return true;
+                        }
+                    }
                 }
             }
         }
diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp
index a5b192c..abe8f85 100644
--- a/libs/WindowManager/Jetpack/Android.bp
+++ b/libs/WindowManager/Jetpack/Android.bp
@@ -55,20 +55,6 @@
 }
 
 // Extensions
-// NOTE: This module is still under active development and must not
-// be used in production. Use 'androidx.window.sidecar' instead.
-android_library_import {
-    name: "window-extensions",
-    aars: ["window-extensions-release.aar"],
-    sdk_version: "current",
-}
-
-android_library_import {
-    name: "window-extensions-core",
-    aars: ["window-extensions-core-release.aar"],
-    sdk_version: "current",
-}
-
 java_library {
     name: "androidx.window.extensions",
     srcs: [
@@ -77,8 +63,8 @@
         "src/androidx/window/common/**/*.java",
     ],
     static_libs: [
-        "window-extensions",
-        "window-extensions-core",
+        "androidx.window.extensions_extensions-nodeps",
+        "androidx.window.extensions.core_core-nodeps",
     ],
     installable: true,
     sdk_version: "core_platform",
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index 8386131..a7a6b3c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -122,16 +122,6 @@
         addWindowLayoutInfoListener(activity, extConsumer);
     }
 
-    @Override
-    public void addWindowLayoutInfoListener(@NonNull @UiContext Context context,
-            @NonNull java.util.function.Consumer<WindowLayoutInfo> consumer) {
-        final Consumer<WindowLayoutInfo> extConsumer = consumer::accept;
-        synchronized (mLock) {
-            mJavaToExtConsumers.put(consumer, extConsumer);
-        }
-        addWindowLayoutInfoListener(context, extConsumer);
-    }
-
     /**
      * Similar to {@link #addWindowLayoutInfoListener(Activity, java.util.function.Consumer)}, but
      * takes a UI Context as a parameter.
diff --git a/libs/WindowManager/Jetpack/window-extensions-core-release.aar b/libs/WindowManager/Jetpack/window-extensions-core-release.aar
deleted file mode 100644
index 96ff840..0000000
--- a/libs/WindowManager/Jetpack/window-extensions-core-release.aar
+++ /dev/null
Binary files differ
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
deleted file mode 100644
index c3b6916..0000000
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ /dev/null
Binary files differ
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index e84a78f..133fd87 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -33,12 +33,19 @@
      *
      * @param touchX the X touch position of the {@link MotionEvent}.
      * @param touchY the Y touch position of the {@link MotionEvent}.
+     * @param velocityX the X velocity computed from the {@link MotionEvent}.
+     * @param velocityY the Y velocity computed from the {@link MotionEvent}.
      * @param keyAction the original {@link KeyEvent#getAction()} when the event was dispatched to
      *               the process. This is forwarded separately because the input pipeline may mutate
      *               the {#event} action state later.
      * @param swipeEdge the edge from which the swipe begins.
      */
-    void onBackMotion(float touchX, float touchY, int keyAction,
+    void onBackMotion(
+            float touchX,
+            float touchY,
+            float velocityX,
+            float velocityY,
+            int keyAction,
             @BackEvent.SwipeEdge int swipeEdge);
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 210c9aa..47d3a5c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -256,8 +256,20 @@
     private class BackAnimationImpl implements BackAnimation {
         @Override
         public void onBackMotion(
-                float touchX, float touchY, int keyAction, @BackEvent.SwipeEdge int swipeEdge) {
-            mShellExecutor.execute(() -> onMotionEvent(touchX, touchY, keyAction, swipeEdge));
+                float touchX,
+                float touchY,
+                float velocityX,
+                float velocityY,
+                int keyAction,
+                @BackEvent.SwipeEdge int swipeEdge
+        ) {
+            mShellExecutor.execute(() -> onMotionEvent(
+                    /* touchX = */ touchX,
+                    /* touchY = */ touchY,
+                    /* velocityX = */ velocityX,
+                    /* velocityY = */ velocityY,
+                    /* keyAction = */ keyAction,
+                    /* swipeEdge = */ swipeEdge));
         }
 
         @Override
@@ -332,13 +344,18 @@
      * Called when a new motion event needs to be transferred to this
      * {@link BackAnimationController}
      */
-    public void onMotionEvent(float touchX, float touchY, int keyAction,
+    public void onMotionEvent(
+            float touchX,
+            float touchY,
+            float velocityX,
+            float velocityY,
+            int keyAction,
             @BackEvent.SwipeEdge int swipeEdge) {
         if (mPostCommitAnimationInProgress) {
             return;
         }
 
-        mTouchTracker.update(touchX, touchY);
+        mTouchTracker.update(touchX, touchY, velocityX, velocityY);
         if (keyAction == MotionEvent.ACTION_DOWN) {
             if (!mBackGestureStarted) {
                 mShouldStartOnNextMoveEvent = true;
@@ -561,6 +578,9 @@
         }
         if (runner.isWaitingAnimation()) {
             ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready.");
+            // Supposed it is in post commit animation state, and start the timeout to watch
+            // if the animation is ready.
+            mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
             return;
         } else if (runner.isAnimationCancelled()) {
             invokeOrCancelBack();
@@ -577,6 +597,8 @@
         if (mPostCommitAnimationInProgress) {
             return;
         }
+
+        mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
         ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startPostCommitAnimation()");
         mPostCommitAnimationInProgress = true;
         mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
@@ -595,9 +617,6 @@
      */
     @VisibleForTesting
     void onBackAnimationFinished() {
-        if (!mPostCommitAnimationInProgress) {
-            return;
-        }
         // Stop timeout runner.
         mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
         mPostCommitAnimationInProgress = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
index 695ef4e..904574b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -42,11 +42,13 @@
      */
     private float mInitTouchX;
     private float mInitTouchY;
+    private float mLatestVelocityX;
+    private float mLatestVelocityY;
     private float mStartThresholdX;
     private int mSwipeEdge;
     private boolean mCancelled;
 
-    void update(float touchX, float touchY) {
+    void update(float touchX, float touchY, float velocityX, float velocityY) {
         /**
          * If back was previously cancelled but the user has started swiping in the forward
          * direction again, restart back.
@@ -58,6 +60,8 @@
         }
         mLatestTouchX = touchX;
         mLatestTouchY = touchY;
+        mLatestVelocityX = velocityX;
+        mLatestVelocityY = velocityY;
     }
 
     void setTriggerBack(boolean triggerBack) {
@@ -84,7 +88,14 @@
     }
 
     BackMotionEvent createStartEvent(RemoteAnimationTarget target) {
-        return new BackMotionEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target);
+        return new BackMotionEvent(
+                /* touchX = */ mInitTouchX,
+                /* touchY = */ mInitTouchY,
+                /* progress = */ 0,
+                /* velocityX = */ 0,
+                /* velocityY = */ 0,
+                /* swipeEdge = */ mSwipeEdge,
+                /* departingAnimationTarget = */ target);
     }
 
     BackMotionEvent createProgressEvent() {
@@ -111,7 +122,14 @@
     }
 
     BackMotionEvent createProgressEvent(float progress) {
-        return new BackMotionEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
+        return new BackMotionEvent(
+                /* touchX = */ mLatestTouchX,
+                /* touchY = */ mLatestTouchY,
+                /* progress = */ progress,
+                /* velocityX = */ mLatestVelocityX,
+                /* velocityY = */ mLatestVelocityY,
+                /* swipeEdge = */ mSwipeEdge,
+                /* departingAnimationTarget = */ null);
     }
 
     public void setProgressThreshold(float progressThreshold) {
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 670b24c..0400963 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
@@ -25,6 +25,7 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
 import android.app.WindowConfiguration.WindowingMode
 import android.content.Context
+import android.graphics.Point
 import android.graphics.Rect
 import android.os.IBinder
 import android.os.SystemProperties
@@ -193,6 +194,21 @@
         }
     }
 
+
+    /**
+     * Move a task to fullscreen after being dragged from fullscreen and released back into
+     * status bar area
+     */
+    fun cancelMoveToFreeform(task: RunningTaskInfo, startPosition: Point) {
+        val wct = WindowContainerTransaction()
+        addMoveToFullscreenChanges(wct, task.token)
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, startPosition)
+        } else {
+            shellTaskOrganizer.applyTransaction(wct)
+        }
+    }
+
     fun moveToFullscreenWithAnimation(task: ActivityManager.RunningTaskInfo) {
         val wct = WindowContainerTransaction()
         addMoveToFullscreenChanges(wct, task.token)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 3df2340..27eda16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -17,11 +17,13 @@
 package com.android.wm.shell.desktopmode;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.app.ActivityManager;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.view.SurfaceControl;
@@ -55,6 +57,7 @@
     public static final int FREEFORM_ANIMATION_DURATION = 336;
 
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
+    private Point mStartPosition;
 
     public EnterDesktopTaskTransitionHandler(
             Transitions transitions) {
@@ -79,6 +82,17 @@
         mPendingTransitionTokens.add(token);
     }
 
+    /**
+     * Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
+     * @param wct WindowContainerTransaction for transition
+     * @param startPosition Position of task when transition is triggered
+     */
+    public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct,
+            Point startPosition) {
+        mStartPosition = startPosition;
+        startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct);
+    }
+
     @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startT,
@@ -173,6 +187,37 @@
             return true;
         }
 
+        if (type == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
+                && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+                && mStartPosition != null) {
+            // This Transition animates a task to fullscreen after being dragged from the status
+            // bar and then released back into the status bar area
+            final SurfaceControl sc = change.getLeash();
+            startT.setWindowCrop(sc, null);
+            startT.apply();
+
+            final ValueAnimator animator = new ValueAnimator();
+            animator.setFloatValues(DRAG_FREEFORM_SCALE, 1f);
+            animator.setDuration(FREEFORM_ANIMATION_DURATION);
+            final SurfaceControl.Transaction t = mTransactionSupplier.get();
+            animator.addUpdateListener(animation -> {
+                final float scale = animation.getAnimatedFraction();
+                t.setPosition(sc, mStartPosition.x * (1 - scale),
+                        mStartPosition.y * (1 - scale));
+                t.setScale(sc, scale, scale);
+                t.apply();
+            });
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mTransitions.getMainExecutor().execute(
+                            () -> finishCallback.onTransitionFinished(null, null));
+                }
+            });
+            animator.start();
+            return true;
+        }
+
         return false;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index f70df83..8c98c77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -30,14 +30,17 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.graphics.Rect;
+import android.os.SystemClock;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.window.TaskSnapshot;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.transition.Transitions;
 
 import java.lang.annotation.Retention;
@@ -61,6 +64,14 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface AnimationType {}
 
+    /**
+     * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if
+     * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button
+     * navigation, then the alpha type is unexpected. So use a timeout to avoid applying wrong
+     * animation style to an unrelated task.
+     */
+    private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 800;
+
     public static final int TRANSITION_DIRECTION_NONE = 0;
     public static final int TRANSITION_DIRECTION_SAME = 1;
     public static final int TRANSITION_DIRECTION_TO_PIP = 2;
@@ -109,6 +120,9 @@
             });
 
     private PipTransitionAnimator mCurrentAnimator;
+    @AnimationType
+    private int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+    private long mLastOneShotAlphaAnimationTime;
 
     public PipAnimationController(PipSurfaceTransactionHelper helper) {
         mSurfaceTransactionHelper = helper;
@@ -222,6 +236,37 @@
     }
 
     /**
+     * Sets the preferred enter animation type for one time. This is typically used to set the
+     * animation type to {@link PipAnimationController#ANIM_TYPE_ALPHA}.
+     * <p>
+     * For example, gesture navigation would first fade out the PiP activity, and the transition
+     * should be responsible to animate in (such as fade in) the PiP.
+     */
+    public void setOneShotEnterAnimationType(@AnimationType int animationType) {
+        mOneShotAnimationType = animationType;
+        if (animationType == ANIM_TYPE_ALPHA) {
+            mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis();
+        }
+    }
+
+    /** Returns the preferred animation type and consumes the one-shot type if needed. */
+    @AnimationType
+    public int takeOneShotEnterAnimationType() {
+        final int type = mOneShotAnimationType;
+        if (type == ANIM_TYPE_ALPHA) {
+            // Restore to default type.
+            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+            if (SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime
+                    > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) {
+                ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                        "Alpha animation is expired. Use bounds animation.");
+                return ANIM_TYPE_BOUNDS;
+            }
+        }
+        return type;
+    }
+
+    /**
      * Additional callback interface for PiP animation
      */
     public static class PipAnimationCallback {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index a0bd064..5670fe6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -62,7 +62,6 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.Log;
 import android.view.Choreographer;
@@ -111,12 +110,6 @@
         DisplayController.OnDisplaysChangedListener, ShellTaskOrganizer.FocusListener {
     private static final String TAG = PipTaskOrganizer.class.getSimpleName();
     private static final boolean DEBUG = false;
-    /**
-     * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if
-     * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button
-     * navigation, then the alpha type is unexpected.
-     */
-    private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 1000;
 
     /**
      * The fixed start delay in ms when fading out the content overlay from bounds animation.
@@ -301,8 +294,6 @@
     private WindowContainerToken mToken;
     private SurfaceControl mLeash;
     protected PipTransitionState mPipTransitionState;
-    private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
-    private long mLastOneShotAlphaAnimationTime;
     private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
             mSurfaceControlTransactionFactory;
     protected PictureInPictureParams mPictureInPictureParams;
@@ -422,18 +413,6 @@
     }
 
     /**
-     * Sets the preferred animation type for one time.
-     * This is typically used to set the animation type to
-     * {@link PipAnimationController#ANIM_TYPE_ALPHA}.
-     */
-    public void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) {
-        mOneShotAnimationType = animationType;
-        if (animationType == ANIM_TYPE_ALPHA) {
-            mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis();
-        }
-    }
-
-    /**
      * Override if the PiP should always use a fade-in animation during PiP entry.
      *
      * @return true if the mOneShotAnimationType should always be
@@ -733,26 +712,17 @@
             return;
         }
 
-        if (mOneShotAnimationType == ANIM_TYPE_ALPHA
-                && SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime
-                > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: Alpha animation is expired. Use bounds animation.", TAG);
-            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
-        }
-
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             // For Shell transition, we will animate the window in PipTransition#startAnimation
             // instead of #onTaskAppeared.
             return;
         }
 
-        if (shouldAlwaysFadeIn()) {
-            mOneShotAnimationType = ANIM_TYPE_ALPHA;
-        }
-
+        final int animationType = shouldAlwaysFadeIn()
+                ? ANIM_TYPE_ALPHA
+                : mPipAnimationController.takeOneShotEnterAnimationType();
         if (mWaitForFixedRotation) {
-            onTaskAppearedWithFixedRotation();
+            onTaskAppearedWithFixedRotation(animationType);
             return;
         }
 
@@ -763,7 +733,7 @@
         Objects.requireNonNull(destinationBounds, "Missing destination bounds");
         final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
 
-        if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
+        if (animationType == ANIM_TYPE_BOUNDS) {
             if (!shouldAttachMenuEarly()) {
                 mPipMenuController.attach(mLeash);
             }
@@ -773,16 +743,15 @@
                     sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration,
                     null /* updateBoundsCallback */);
             mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
-        } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+        } else if (animationType == ANIM_TYPE_ALPHA) {
             enterPipWithAlphaAnimation(destinationBounds, mEnterAnimationDuration);
-            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
         } else {
-            throw new RuntimeException("Unrecognized animation type: " + mOneShotAnimationType);
+            throw new RuntimeException("Unrecognized animation type: " + animationType);
         }
     }
 
-    private void onTaskAppearedWithFixedRotation() {
-        if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+    private void onTaskAppearedWithFixedRotation(int animationType) {
+        if (animationType == ANIM_TYPE_ALPHA) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: Defer entering PiP alpha animation, fixed rotation is ongoing", TAG);
             // If deferred, hside the surface till fixed rotation is completed.
@@ -791,7 +760,6 @@
             tx.setAlpha(mLeash, 0f);
             tx.show(mLeash);
             tx.apply();
-            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
             return;
         }
         final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
@@ -1895,7 +1863,6 @@
                 + " binder=" + (mToken != null ? mToken.asBinder() : null));
         pw.println(innerPrefix + "mLeash=" + mLeash);
         pw.println(innerPrefix + "mState=" + mPipTransitionState.getTransitionState());
-        pw.println(innerPrefix + "mOneShotAnimationType=" + mOneShotAnimationType);
         pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index dcb4b84..b743140 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -87,7 +87,7 @@
     private final int mEnterExitAnimationDuration;
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
     private final Optional<SplitScreenController> mSplitScreenOptional;
-    private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+    private @PipAnimationController.AnimationType int mEnterAnimationType = ANIM_TYPE_BOUNDS;
     private Transitions.TransitionFinishCallback mFinishCallback;
     private SurfaceControl.Transaction mFinishTransaction;
     private final Rect mExitDestinationBounds = new Rect();
@@ -133,20 +133,6 @@
     }
 
     @Override
-    public void setIsFullAnimation(boolean isFullAnimation) {
-        setOneShotAnimationType(isFullAnimation ? ANIM_TYPE_BOUNDS : ANIM_TYPE_ALPHA);
-    }
-
-    /**
-     * Sets the preferred animation type for one time.
-     * This is typically used to set the animation type to
-     * {@link PipAnimationController#ANIM_TYPE_ALPHA}.
-     */
-    private void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) {
-        mOneShotAnimationType = animationType;
-    }
-
-    @Override
     public void startExitTransition(int type, WindowContainerTransaction out,
             @Nullable Rect destinationBounds) {
         if (destinationBounds != null) {
@@ -288,7 +274,10 @@
         if (!requestHasPipEnter(request)) {
             throw new IllegalStateException("Called PiP augmentRequest when request has no PiP");
         }
-        if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+        mEnterAnimationType = mPipOrganizer.shouldAlwaysFadeIn()
+                ? ANIM_TYPE_ALPHA
+                : mPipAnimationController.takeOneShotEnterAnimationType();
+        if (mEnterAnimationType == ANIM_TYPE_ALPHA) {
             mRequestedEnterTransition = transition;
             mRequestedEnterTask = request.getTriggerTask().token;
             outWCT.setActivityWindowingMode(request.getTriggerTask().token,
@@ -308,7 +297,7 @@
     @Override
     public boolean handleRotateDisplay(int startRotation, int endRotation,
             WindowContainerTransaction wct) {
-        if (mRequestedEnterTransition != null && mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+        if (mRequestedEnterTransition != null && mEnterAnimationType == ANIM_TYPE_ALPHA) {
             // A fade-in was requested but not-yet started. In this case, just recalculate the
             // initial state under the new rotation.
             int rotationDelta = deltaRotation(startRotation, endRotation);
@@ -760,7 +749,6 @@
         if (taskInfo.pictureInPictureParams != null
                 && taskInfo.pictureInPictureParams.isAutoEnterEnabled()
                 && mPipTransitionState.getInSwipePipToHomeTransition()) {
-            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
             final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay;
             startTransaction.setMatrix(leash, Matrix.IDENTITY_MATRIX, new float[9])
                     .setPosition(leash, destinationBounds.left, destinationBounds.top)
@@ -796,17 +784,16 @@
             startTransaction.setMatrix(leash, tmpTransform, new float[9]);
         }
 
-        if (mPipOrganizer.shouldAlwaysFadeIn()) {
-            mOneShotAnimationType = ANIM_TYPE_ALPHA;
-        }
-
-        if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+        final int enterAnimationType = mEnterAnimationType;
+        if (enterAnimationType == ANIM_TYPE_ALPHA) {
+            // Restore to default type.
+            mEnterAnimationType = ANIM_TYPE_BOUNDS;
             startTransaction.setAlpha(leash, 0f);
         }
         startTransaction.apply();
 
         PipAnimationController.PipTransitionAnimator animator;
-        if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
+        if (enterAnimationType == ANIM_TYPE_BOUNDS) {
             animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds,
                     currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
                     0 /* startingAngle */, rotationDelta);
@@ -829,13 +816,11 @@
                     animator.setColorContentOverlay(mContext);
                 }
             }
-        } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+        } else if (enterAnimationType == ANIM_TYPE_ALPHA) {
             animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,
                     0f, 1f);
-            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
         } else {
-            throw new RuntimeException("Unrecognized animation type: "
-                    + mOneShotAnimationType);
+            throw new RuntimeException("Unrecognized animation type: " + enterAnimationType);
         }
         animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
                 .setPipAnimationCallback(mPipAnimationCallback)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index f51e247..7979ce7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -105,15 +105,6 @@
     }
 
     /**
-     * Called to inform the transition that the animation should start with the assumption that
-     * PiP is not animating from its original bounds, but rather a continuation of another
-     * animation. For example, gesture navigation would first fade out the PiP activity, and the
-     * transition should be responsible to animate in (such as fade in) the PiP.
-     */
-    public void setIsFullAnimation(boolean isFullAnimation) {
-    }
-
-    /**
      * Called when the Shell wants to start an exit Pip transition/animation.
      */
     public void startExitTransition(int type, WindowContainerTransaction out,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 463ad77..b0bb14b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -968,12 +968,6 @@
         mPipBoundsState.setShelfVisibility(visible, shelfHeight);
     }
 
-    private void setPinnedStackAnimationType(int animationType) {
-        mPipTaskOrganizer.setOneShotAnimationType(animationType);
-        mPipTransitionController.setIsFullAnimation(
-                animationType == PipAnimationController.ANIM_TYPE_BOUNDS);
-    }
-
     @VisibleForTesting
     void setPinnedStackAnimationListener(PipAnimationListener callback) {
         mPinnedStackAnimationRecentsCallback = callback;
@@ -1337,7 +1331,8 @@
         @Override
         public void setPipAnimationTypeToAlpha() {
             executeRemoteCallWithTaskPermission(mController, "setPipAnimationTypeToAlpha",
-                    (controller) -> controller.setPinnedStackAnimationType(ANIM_TYPE_ALPHA));
+                    (controller) -> controller.mPipAnimationController.setOneShotEnterAnimationType(
+                            ANIM_TYPE_ALPHA));
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 81e118a..f819bee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -129,9 +129,10 @@
     /**
      * Start a pair of intents in one transition.
      */
-    oneway void startIntents(in PendingIntent pendingIntent1, in Bundle options1,
-            in PendingIntent pendingIntent2, in Bundle options2, int splitPosition,
-            float splitRatio, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
+    oneway void startIntents(in PendingIntent pendingIntent1, in ShortcutInfo shortcutInfo1,
+            in Bundle options1, in PendingIntent pendingIntent2, in ShortcutInfo shortcutInfo2,
+            in Bundle options2, int splitPosition, float splitRatio,
+            in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
 
     /**
      * Blocking call that notifies and gets additional split-screen targets when entering
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 7d5ab84..2cd16be 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -626,6 +626,35 @@
                 splitPosition, splitRatio, adapter, instanceId);
     }
 
+    private void startIntents(PendingIntent pendingIntent1,
+            @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+            PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2,
+            @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+            @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+        Intent fillInIntent1 = null;
+        Intent fillInIntent2 = null;
+        final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
+        final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2);
+        if (samePackage(packageName1, packageName2)) {
+            if (supportMultiInstancesSplit(packageName1)) {
+                fillInIntent1 = new Intent();
+                fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                fillInIntent2 = new Intent();
+                fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+            } else {
+                pendingIntent2 = null;
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                        "Cancel entering split as not supporting multi-instances");
+                Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+                        Toast.LENGTH_SHORT).show();
+            }
+        }
+        mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1, options1,
+                pendingIntent2, fillInIntent2, shortcutInfo2, options2, splitPosition, splitRatio,
+                remoteTransition, instanceId);
+    }
+
     @Override
     public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
             @SplitPosition int position, @Nullable Bundle options) {
@@ -1066,11 +1095,17 @@
         }
 
         @Override
-        public void startIntents(PendingIntent pendingIntent1, @Nullable Bundle options1,
-                PendingIntent pendingIntent2, @Nullable Bundle options2,
+        public void startIntents(PendingIntent pendingIntent1, @Nullable ShortcutInfo shortcutInfo1,
+                @Nullable Bundle options1, PendingIntent pendingIntent2,
+                @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
                 @SplitPosition int splitPosition, float splitRatio,
                 @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
-            // TODO(b/259368992): To be implemented.
+            executeRemoteCallWithTaskPermission(mController, "startIntents",
+                    (controller) ->
+                            controller.startIntents(pendingIntent1, shortcutInfo1,
+                                    options1, pendingIntent2, shortcutInfo2, options2,
+                                    splitPosition, splitRatio, remoteTransition, instanceId)
+            );
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index db9af27..ce5a2af6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -682,6 +682,46 @@
         setEnterInstanceId(instanceId);
     }
 
+    void startIntents(PendingIntent pendingIntent1, Intent fillInIntent1,
+            @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+            PendingIntent pendingIntent2, Intent fillInIntent2,
+            @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
+            @SplitPosition int splitPosition, float splitRatio,
+            @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        if (!mMainStage.isActive()) {
+            // Build a request WCT that will launch both apps such that task 0 is on the main stage
+            // while task 1 is on the side stage.
+            mMainStage.activate(wct, false /* reparent */);
+        }
+
+        prepareEvictChildTasksIfSplitActive(wct);
+        mSplitLayout.setDivideRatio(splitRatio);
+        updateWindowBounds(mSplitLayout, wct);
+        wct.reorder(mRootTaskInfo.token, true);
+        setRootForceTranslucent(false, wct);
+
+        setSideStagePosition(splitPosition, wct);
+        options1 = options1 != null ? options1 : new Bundle();
+        addActivityOptions(options1, mSideStage);
+        if (shortcutInfo1 != null) {
+            wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
+        } else {
+            wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+        }
+        options2 = options2 != null ? options2 : new Bundle();
+        addActivityOptions(options2, mMainStage);
+        if (shortcutInfo2 != null) {
+            wct.startShortcut(mContext.getPackageName(), shortcutInfo2, options2);
+        } else {
+            wct.sendPendingIntent(pendingIntent2, fillInIntent2, options2);
+        }
+
+        mSplitTransitions.startEnterTransition(
+                TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null, null);
+        setEnterInstanceId(instanceId);
+    }
+
     /** Starts a pair of tasks using legacy transition. */
     void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
             int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index fa4de16..bdb7d44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -143,6 +143,10 @@
     /** Transition type to fullscreen from desktop mode. */
     public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12;
 
+    /** Transition type to animate back to fullscreen when drag to freeform is cancelled. */
+    public static final int TRANSIT_CANCEL_ENTERING_DESKTOP_MODE =
+            WindowManager.TRANSIT_FIRST_CUSTOM + 13;
+
     private final WindowOrganizer mOrganizer;
     private final Context mContext;
     private final ShellExecutor mMainExecutor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index afc573e..5226eee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -34,6 +34,7 @@
 import android.app.ActivityTaskManager;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.input.InputManager;
 import android.os.Handler;
@@ -557,8 +558,10 @@
                         mDragToDesktopAnimationStarted = false;
                         return;
                     } else if (mDragToDesktopAnimationStarted) {
-                        mDesktopTasksController.ifPresent(c ->
-                                c.moveToFullscreen(relevantDecor.mTaskInfo));
+                        Point startPosition = new Point((int) ev.getX(), (int) ev.getY());
+                        mDesktopTasksController.ifPresent(
+                                c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo,
+                                        startPosition));
                         mDragToDesktopAnimationStarted = false;
                         return;
                     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index a004e37..67e99d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -42,6 +42,7 @@
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.TextView;
+import android.window.SurfaceSyncGroup;
 import android.window.WindowContainerTransaction;
 
 import com.android.launcher3.icons.IconProvider;
@@ -311,51 +312,50 @@
      * Create and display handle menu window
      */
     void createHandleMenu() {
+        final SurfaceSyncGroup ssg = new SurfaceSyncGroup(TAG);
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         updateHandleMenuPillPositions();
 
-        createAppInfoPill(t);
+        createAppInfoPill(t, ssg);
 
         // Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
         final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
         if (shouldShowWindowingPill) {
-            createWindowingPill(t);
+            createWindowingPill(t, ssg);
         }
 
-        createMoreActionsPill(t);
+        createMoreActionsPill(t, ssg);
 
-        mSyncQueue.runInSync(transaction -> {
-            transaction.merge(t);
-            t.close();
-        });
+        ssg.addTransaction(t);
+        ssg.markSyncReady();
         setupHandleMenu(shouldShowWindowingPill);
     }
 
-    private void createAppInfoPill(SurfaceControl.Transaction t) {
+    private void createAppInfoPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
         final int x = (int) mHandleMenuAppInfoPillPosition.x;
         final int y = (int) mHandleMenuAppInfoPillPosition.y;
         mHandleMenuAppInfoPill = addWindow(
                 R.layout.desktop_mode_window_decor_handle_menu_app_info_pill,
                 "Menu's app info pill",
-                t, x, y, mMenuWidth, mAppInfoPillHeight, mShadowRadius, mCornerRadius);
+                t, ssg, x, y, mMenuWidth, mAppInfoPillHeight, mShadowRadius, mCornerRadius);
     }
 
-    private void createWindowingPill(SurfaceControl.Transaction t) {
+    private void createWindowingPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
         final int x = (int) mHandleMenuWindowingPillPosition.x;
         final int y = (int) mHandleMenuWindowingPillPosition.y;
         mHandleMenuWindowingPill = addWindow(
                 R.layout.desktop_mode_window_decor_handle_menu_windowing_pill,
                 "Menu's windowing pill",
-                t, x, y, mMenuWidth, mWindowingPillHeight, mShadowRadius, mCornerRadius);
+                t, ssg, x, y, mMenuWidth, mWindowingPillHeight, mShadowRadius, mCornerRadius);
     }
 
-    private void createMoreActionsPill(SurfaceControl.Transaction t) {
+    private void createMoreActionsPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
         final int x = (int) mHandleMenuMoreActionsPillPosition.x;
         final int y = (int) mHandleMenuMoreActionsPillPosition.y;
         mHandleMenuMoreActionsPill = addWindow(
                 R.layout.desktop_mode_window_decor_handle_menu_more_actions_pill,
                 "Menu's more actions pill",
-                t, x, y, mMenuWidth, mMoreActionsPillHeight, mShadowRadius, mCornerRadius);
+                t, ssg, x, y, mMenuWidth, mMoreActionsPillHeight, mShadowRadius, mCornerRadius);
     }
 
     private void setupHandleMenu(boolean windowingPillShown) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index bc5fd4d..19a3182 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -34,6 +34,7 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowlessWindowManager;
+import android.window.SurfaceSyncGroup;
 import android.window.TaskConstants;
 import android.window.WindowContainerTransaction;
 
@@ -192,13 +193,13 @@
             mDecorWindowContext = mContext.createConfigurationContext(taskConfig);
             if (params.mLayoutResId != 0) {
                 outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
-                                .inflate(params.mLayoutResId, null);
+                        .inflate(params.mLayoutResId, null);
             }
         }
 
         if (outResult.mRootView == null) {
             outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
-                            .inflate(params.mLayoutResId , null);
+                    .inflate(params.mLayoutResId, null);
         }
 
         // DecorationContainerSurface
@@ -382,18 +383,20 @@
     /**
      * Create a window associated with this WindowDecoration.
      * Note that subclass must dispose of this when the task is hidden/closed.
-     * @param layoutId layout to make the window from
-     * @param t the transaction to apply
-     * @param xPos x position of new window
-     * @param yPos y position of new window
-     * @param width width of new window
-     * @param height height of new window
+     *
+     * @param layoutId     layout to make the window from
+     * @param t            the transaction to apply
+     * @param xPos         x position of new window
+     * @param yPos         y position of new window
+     * @param width        width of new window
+     * @param height       height of new window
      * @param shadowRadius radius of the shadow of the new window
      * @param cornerRadius radius of the corners of the new window
      * @return the {@link AdditionalWindow} that was added.
      */
     AdditionalWindow addWindow(int layoutId, String namePrefix, SurfaceControl.Transaction t,
-            int xPos, int yPos, int width, int height, int shadowRadius, int cornerRadius) {
+            SurfaceSyncGroup ssg, int xPos, int yPos, int width, int height, int shadowRadius,
+            int cornerRadius) {
         final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
         SurfaceControl windowSurfaceControl = builder
                 .setName(namePrefix + " of Task=" + mTaskInfo.taskId)
@@ -417,12 +420,12 @@
                 windowSurfaceControl, null /* hostInputToken */);
         SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory
                 .create(mDecorWindowContext, mDisplay, windowManager);
-        viewHost.setView(v, lp);
+        ssg.add(viewHost.getSurfacePackage(), () -> viewHost.setView(v, lp));
         return new AdditionalWindow(windowSurfaceControl, viewHost,
                 mSurfaceControlTransactionSupplier);
     }
 
-    static class RelayoutParams{
+    static class RelayoutParams {
         RunningTaskInfo mRunningTaskInfo;
         int mLayoutResId;
         int mCaptionHeightId;
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index 67ca9a1..b6d92814 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -29,6 +29,7 @@
         <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
         <option name="teardown-command" value="settings delete system show_touches" />
         <option name="teardown-command" value="settings delete system pointer_location" />
+        <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 806bffe..d95c7a4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -166,6 +166,9 @@
         doMotionEvent(MotionEvent.ACTION_DOWN, 0);
         doMotionEvent(MotionEvent.ACTION_MOVE, 0);
         mController.setTriggerBack(true);
+    }
+
+    private void releaseBackGesture() {
         doMotionEvent(MotionEvent.ACTION_UP, 0);
     }
 
@@ -201,6 +204,8 @@
                     .setOnBackNavigationDone(new RemoteCallback(result)));
             triggerBackGesture();
             simulateRemoteAnimationStart(type);
+            mShellExecutor.flushAll();
+            releaseBackGesture();
             simulateRemoteAnimationFinished();
             mShellExecutor.flushAll();
 
@@ -252,6 +257,7 @@
         createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false);
 
         triggerBackGesture();
+        releaseBackGesture();
 
         verify(mAppCallback, times(1)).onBackInvoked();
 
@@ -269,6 +275,8 @@
 
         triggerBackGesture();
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+        releaseBackGesture();
+
         // Check that back invocation is dispatched.
         verify(mAnimatorCallback).onBackInvoked();
         verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
@@ -308,6 +316,9 @@
 
         triggerBackGesture();
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+        mShellExecutor.flushAll();
+
+        releaseBackGesture();
 
         // Simulate transition timeout.
         mShellExecutor.flushAll();
@@ -369,6 +380,9 @@
             simulateRemoteAnimationStart(type);
             mShellExecutor.flushAll();
 
+            releaseBackGesture();
+            mShellExecutor.flushAll();
+
             assertTrue("Navigation Done callback not called for "
                     + BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
             assertTrue("TriggerBack should have been true", result.mTriggerBack);
@@ -395,6 +409,8 @@
                 .setOnBackNavigationDone(new RemoteCallback(result)));
         triggerBackGesture();
         mShellExecutor.flushAll();
+        releaseBackGesture();
+        mShellExecutor.flushAll();
 
         assertTrue("Navigation Done callback not called for "
                 + BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
@@ -458,9 +474,12 @@
 
     private void doMotionEvent(int actionDown, int coordinate) {
         mController.onMotionEvent(
-                coordinate, coordinate,
-                actionDown,
-                BackEvent.EDGE_LEFT);
+                /* touchX */ coordinate,
+                /* touchY */ coordinate,
+                /* velocityX = */ 0,
+                /* velocityY = */ 0,
+                /* keyAction */ actionDown,
+                /* swipeEdge */ BackEvent.EDGE_LEFT);
     }
 
     private void simulateRemoteAnimationStart(int type) throws RemoteException {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 3608474..874ef80 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -16,8 +16,6 @@
 
 package com.android.wm.shell.back;
 
-import static android.window.BackEvent.EDGE_LEFT;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
@@ -48,12 +46,21 @@
     private CountDownLatch mTargetProgressCalled = new CountDownLatch(1);
     private Handler mMainThreadHandler;
 
+    private BackMotionEvent backMotionEventFrom(float touchX, float progress) {
+        return new BackMotionEvent(
+                /* touchX = */ touchX,
+                /* touchY = */ 0,
+                /* progress = */ progress,
+                /* velocityX = */ 0,
+                /* velocityY = */ 0,
+                /* swipeEdge = */ BackEvent.EDGE_LEFT,
+                /* departingAnimationTarget = */ null);
+    }
+
     @Before
     public void setUp() throws Exception {
         mMainThreadHandler = new Handler(Looper.getMainLooper());
-        final BackMotionEvent backEvent = new BackMotionEvent(
-                0, 0,
-                0, EDGE_LEFT, null);
+        final BackMotionEvent backEvent = backMotionEventFrom(0, 0);
         mMainThreadHandler.post(
                 () -> {
                     mProgressAnimator = new BackProgressAnimator();
@@ -63,9 +70,7 @@
 
     @Test
     public void testBackProgressed() throws InterruptedException {
-        final BackMotionEvent backEvent = new BackMotionEvent(
-                100, 0,
-                mTargetProgress, EDGE_LEFT, null);
+        final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress);
         mMainThreadHandler.post(
                 () -> mProgressAnimator.onBackProgressed(backEvent));
 
@@ -78,9 +83,7 @@
     @Test
     public void testBackCancelled() throws InterruptedException {
         // Give the animator some progress.
-        final BackMotionEvent backEvent = new BackMotionEvent(
-                100, 0,
-                mTargetProgress, EDGE_LEFT, null);
+        final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress);
         mMainThreadHandler.post(
                 () -> mProgressAnimator.onBackProgressed(backEvent));
         mTargetProgressCalled.await(1, TimeUnit.SECONDS);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
index ba9c159..d62e660 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
@@ -47,43 +47,45 @@
     public void generatesProgress_leftEdge() {
         mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
         float touchX = 10;
+        float velocityX = 0;
+        float velocityY = 0;
 
         // Pre-commit
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
 
         // Post-commit
         touchX += 100;
         mTouchTracker.setTriggerBack(true);
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
 
         // Cancel
         touchX -= 10;
         mTouchTracker.setTriggerBack(false);
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), 0, 0f);
 
         // Cancel more
         touchX -= 10;
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), 0, 0f);
 
         // Restart
         touchX += 10;
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), 0, 0f);
 
         // Restarted, but pre-commit
         float restartX = touchX;
         touchX += 10;
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (touchX - restartX) / FAKE_THRESHOLD, 0f);
 
         // Restarted, post-commit
         touchX += 10;
         mTouchTracker.setTriggerBack(true);
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
     }
 
@@ -91,43 +93,45 @@
     public void generatesProgress_rightEdge() {
         mTouchTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0, BackEvent.EDGE_RIGHT);
         float touchX = INITIAL_X_RIGHT_EDGE - 10; // Fake right edge
+        float velocityX = 0f;
+        float velocityY = 0f;
 
         // Pre-commit
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
 
         // Post-commit
         touchX -= 100;
         mTouchTracker.setTriggerBack(true);
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
 
         // Cancel
         touchX += 10;
         mTouchTracker.setTriggerBack(false);
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), 0, 0f);
 
         // Cancel more
         touchX += 10;
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), 0, 0f);
 
         // Restart
         touchX -= 10;
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), 0, 0f);
 
         // Restarted, but pre-commit
         float restartX = touchX;
         touchX -= 10;
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (restartX - touchX) / FAKE_THRESHOLD, 0f);
 
         // Restarted, post-commit
         touchX -= 10;
         mTouchTracker.setTriggerBack(true);
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 15bb10e..842c699 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -262,7 +262,8 @@
         DisplayLayout layout = new DisplayLayout(info,
                 mContext.getResources(), true, true);
         mPipDisplayLayoutState.setDisplayLayout(layout);
-        mPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA);
+        doReturn(PipAnimationController.ANIM_TYPE_ALPHA).when(mMockPipAnimationController)
+                .takeOneShotEnterAnimationType();
         mPipTaskOrganizer.setSurfaceControlTransactionFactory(
                 MockSurfaceControlHelper::createMockSurfaceControlTransaction);
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index e8147ff..38a519a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -49,6 +49,7 @@
 import android.view.ViewRootImpl;
 import android.view.WindowInsets;
 import android.view.WindowManager.LayoutParams;
+import android.window.SurfaceSyncGroup;
 import android.window.TaskConstants;
 import android.window.WindowContainerTransaction;
 
@@ -100,6 +101,8 @@
     private TestView mMockView;
     @Mock
     private WindowContainerTransaction mMockWindowContainerTransaction;
+    @Mock
+    private SurfaceSyncGroup mMockSurfaceSyncGroup;
 
     private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions =
             new ArrayList<>();
@@ -553,7 +556,7 @@
             String name = "Test Window";
             WindowDecoration.AdditionalWindow additionalWindow =
                     addWindow(R.layout.desktop_mode_window_decor_handle_menu_app_info_pill, name,
-                            mMockSurfaceControlAddWindowT, x, y,
+                            mMockSurfaceControlAddWindowT, mMockSurfaceSyncGroup, x, y,
                             width, height, shadowRadius, cornerRadius);
             return additionalWindow;
         }
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index 23611ef..7e9d44f 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -117,12 +117,8 @@
     // flush and submit all work to the gpu and wait for it to finish
     mGrContext->flushAndSubmit(/*syncCpu=*/true);
 
-    if (!Properties::isHighEndGfx && mode >= TrimLevel::MODERATE) {
-        mode = TrimLevel::COMPLETE;
-    }
-
     switch (mode) {
-        case TrimLevel::COMPLETE:
+        case TrimLevel::BACKGROUND:
             mGrContext->freeGpuResources();
             SkGraphics::PurgeAllCaches();
             mRenderThread.destroyRenderingContext();
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 1a3e54d..afa0a32 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -391,15 +391,6 @@
          * @hide
          */
         @Override
-        public void onError(int status) {
-            Slog.d(TAG, "onError()" + status);
-            mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
-        }
-
-        /**
-         * @hide
-         */
-        @Override
         public void onRecognitionPaused() {
             Slog.d(TAG, "onRecognitionPaused()");
             mHandler.sendEmptyMessage(MSG_DETECTION_PAUSE);
@@ -413,6 +404,42 @@
             Slog.d(TAG, "onRecognitionResumed()");
             mHandler.sendEmptyMessage(MSG_DETECTION_RESUME);
         }
+
+        /**
+         * @hide
+         */
+        @Override
+        public void onPreempted() {
+            Slog.d(TAG, "onPreempted()");
+            mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public void onModuleDied() {
+            Slog.d(TAG, "onModuleDied()");
+            mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public void onResumeFailed(int status) {
+            Slog.d(TAG, "onResumeFailed()" + status);
+            mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public void onPauseFailed(int status) {
+            Slog.d(TAG, "onPauseFailed()" + status);
+            mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
+        }
     }
 
     private class MyHandler extends Handler {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index e53e956..a9bee03 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -107,7 +107,7 @@
 
         initialUiState = when (requestInfo?.type) {
             RequestInfo.TYPE_CREATE -> {
-                val defaultProviderId = userConfigRepo.getDefaultProviderId()
+                val defaultProviderIdSetByUser = userConfigRepo.getDefaultProviderId()
                 val isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
                 val providerEnableListUiState = getCreateProviderEnableListInitialUiState()
                 val providerDisableListUiState = getCreateProviderDisableListInitialUiState()
@@ -115,12 +115,14 @@
                     getCreateRequestDisplayInfoInitialUiState(originName)!!
                 UiState(
                     createCredentialUiState = CreateFlowUtils.toCreateCredentialUiState(
-                        providerEnableListUiState,
-                        providerDisableListUiState,
-                        defaultProviderId,
-                        requestDisplayInfoUiState,
+                        enabledProviders = providerEnableListUiState,
+                        disabledProviders = providerDisableListUiState,
+                        defaultProviderIdPreferredByApp =
+                        requestDisplayInfoUiState.appPreferredDefaultProviderId,
+                        defaultProviderIdSetByUser = defaultProviderIdSetByUser,
+                        requestDisplayInfo = requestDisplayInfoUiState,
                         isOnPasskeyIntroStateAlready = false,
-                        isPasskeyFirstUse
+                        isPasskeyFirstUse = isPasskeyFirstUse,
                     )!!,
                     getCredentialUiState = null,
                     cancelRequestState = cancelUiRequestState
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 29ec970..4d2bb4c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -250,9 +250,15 @@
             return
         }
         val newUiState = CreateFlowUtils.toCreateCredentialUiState(
-            prevUiState.enabledProviders, prevUiState.disabledProviders,
-            userConfigRepo.getDefaultProviderId(), prevUiState.requestDisplayInfo, true,
-            userConfigRepo.getIsPasskeyFirstUse())
+            enabledProviders = prevUiState.enabledProviders,
+            disabledProviders = prevUiState.disabledProviders,
+            defaultProviderIdPreferredByApp =
+            prevUiState.requestDisplayInfo.appPreferredDefaultProviderId,
+            defaultProviderIdSetByUser = userConfigRepo.getDefaultProviderId(),
+            requestDisplayInfo = prevUiState.requestDisplayInfo,
+            isOnPasskeyIntroStateAlready = true,
+            isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
+        )
         if (newUiState == null) {
             Log.d(Constants.LOG_TAG, "Unable to update create ui state")
             onInternalError()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 64addf2..e8d3b1f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -464,6 +464,9 @@
                 createCredentialRequest.candidateQueryData,
                 createCredentialRequest.isSystemProviderRequired
             )
+            val appPreferredDefaultProviderId: String? =
+                if (!requestInfo.hasPermissionToOverrideDefault()) null
+                else createCredentialRequestJetpack?.displayInfo?.preferDefaultProvider
             return when (createCredentialRequestJetpack) {
                 is CreatePasswordRequest -> RequestDisplayInfo(
                     createCredentialRequestJetpack.id,
@@ -472,6 +475,7 @@
                     appLabel,
                     context.getDrawable(R.drawable.ic_password_24) ?: return null,
                     preferImmediatelyAvailableCredentials = false,
+                    appPreferredDefaultProviderId = appPreferredDefaultProviderId,
                 )
                 is CreatePublicKeyCredentialRequest -> {
                     newRequestDisplayInfoFromPasskeyJson(
@@ -480,6 +484,7 @@
                         context = context,
                         preferImmediatelyAvailableCredentials =
                         createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
+                        appPreferredDefaultProviderId = appPreferredDefaultProviderId,
                     )
                 }
                 is CreateCustomCredentialRequest -> {
@@ -495,6 +500,7 @@
                         typeIcon = displayInfo.credentialTypeIcon?.loadDrawable(context)
                             ?: context.getDrawable(R.drawable.ic_other_sign_in_24) ?: return null,
                         preferImmediatelyAvailableCredentials = false,
+                        appPreferredDefaultProviderId = appPreferredDefaultProviderId,
                     )
                 }
                 else -> null
@@ -504,20 +510,27 @@
         fun toCreateCredentialUiState(
             enabledProviders: List<EnabledProviderInfo>,
             disabledProviders: List<DisabledProviderInfo>?,
-            defaultProviderId: String?,
+            defaultProviderIdPreferredByApp: String?,
+            defaultProviderIdSetByUser: String?,
             requestDisplayInfo: RequestDisplayInfo,
             isOnPasskeyIntroStateAlready: Boolean,
             isPasskeyFirstUse: Boolean,
         ): CreateCredentialUiState? {
             var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
             var remoteEntry: RemoteInfo? = null
-            var defaultProvider: EnabledProviderInfo? = null
+            var defaultProviderPreferredByApp: EnabledProviderInfo? = null
+            var defaultProviderSetByUser: EnabledProviderInfo? = null
             var createOptionsPairs:
                 MutableList<Pair<CreateOptionInfo, EnabledProviderInfo>> = mutableListOf()
             enabledProviders.forEach { enabledProvider ->
-                if (defaultProviderId != null) {
-                    if (enabledProvider.id == defaultProviderId) {
-                        defaultProvider = enabledProvider
+                if (defaultProviderIdPreferredByApp != null) {
+                    if (enabledProvider.id == defaultProviderIdPreferredByApp) {
+                        defaultProviderPreferredByApp = enabledProvider
+                    }
+                }
+                if (defaultProviderIdSetByUser != null) {
+                    if (enabledProvider.id == defaultProviderIdSetByUser) {
+                        defaultProviderSetByUser = enabledProvider
                     }
                 }
                 if (enabledProvider.createOptions.isNotEmpty()) {
@@ -536,12 +549,14 @@
                     remoteEntry = currRemoteEntry
                 }
             }
+            val defaultProvider = defaultProviderPreferredByApp ?: defaultProviderSetByUser
             val initialScreenState = toCreateScreenState(
-                /*createOptionSize=*/createOptionsPairs.size,
-                /*isOnPasskeyIntroStateAlready=*/isOnPasskeyIntroStateAlready,
-                /*requestDisplayInfo=*/requestDisplayInfo,
-                /*defaultProvider=*/defaultProvider, /*remoteEntry=*/remoteEntry,
-                /*isPasskeyFirstUse=*/isPasskeyFirstUse
+                createOptionSize = createOptionsPairs.size,
+                isOnPasskeyIntroStateAlready = isOnPasskeyIntroStateAlready,
+                requestDisplayInfo = requestDisplayInfo,
+                defaultProvider = defaultProvider,
+                remoteEntry = remoteEntry,
+                isPasskeyFirstUse = isPasskeyFirstUse
             ) ?: return null
             return CreateCredentialUiState(
                 enabledProviders = enabledProviders,
@@ -667,6 +682,7 @@
             appLabel: String,
             context: Context,
             preferImmediatelyAvailableCredentials: Boolean,
+            appPreferredDefaultProviderId: String?,
         ): RequestDisplayInfo? {
             val json = JSONObject(requestJson)
             var passkeyUsername = ""
@@ -687,6 +703,7 @@
                 appLabel,
                 context.getDrawable(R.drawable.ic_passkey_24) ?: return null,
                 preferImmediatelyAvailableCredentials,
+                appPreferredDefaultProviderId,
             )
         }
     }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
index 14bf4f2..2df0c7a9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
@@ -27,8 +27,12 @@
 import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 
 @Composable
-fun CredentialListSectionHeader(text: String) {
-    InternalSectionHeader(text, LocalAndroidColorScheme.current.colorOnSurfaceVariant)
+fun CredentialListSectionHeader(text: String, isFirstSection: Boolean) {
+    InternalSectionHeader(
+        text = text,
+        color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+        applyTopPadding = !isFirstSection
+    )
 }
 
 @Composable
@@ -37,8 +41,10 @@
 }
 
 @Composable
-private fun InternalSectionHeader(text: String, color: Color) {
-    Row(modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(top = 8.dp)) {
+private fun InternalSectionHeader(text: String, color: Color, applyTopPadding: Boolean = false) {
+    Row(modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(
+        top = if (applyTopPadding) 8.dp else 0.dp
+    )) {
         SectionHeaderText(
             text,
             modifier = Modifier.padding(top = 20.dp, bottom = 8.dp),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 66d7db8..96010cc 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -435,7 +435,7 @@
     }
     SheetContainerCard {
         item { HeadlineIcon(imageVector = Icons.Outlined.NewReleases) }
-        item { Divider(thickness = 24.dp, color = Color.Transparent) }
+        item { Divider(thickness = 16.dp, color = Color.Transparent) }
         item {
             HeadlineText(
                 text = stringResource(
@@ -633,6 +633,7 @@
             }
         }
         item {
+            Divider(thickness = 8.dp, color = Color.Transparent)
             MoreAboutPasskeySectionHeader(
                 text = stringResource(R.string.public_key_cryptography_title)
             )
@@ -641,6 +642,7 @@
             }
         }
         item {
+            Divider(thickness = 8.dp, color = Color.Transparent)
             MoreAboutPasskeySectionHeader(
                 text = stringResource(R.string.improved_account_security_title)
             )
@@ -649,6 +651,7 @@
             }
         }
         item {
+            Divider(thickness = 8.dp, color = Color.Transparent)
             MoreAboutPasskeySectionHeader(
                 text = stringResource(R.string.seamless_transition_title)
             )
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 4332fb3..12bb629 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -107,6 +107,7 @@
   val appName: String,
   val typeIcon: Drawable,
   val preferImmediatelyAvailableCredentials: Boolean,
+  val appPreferredDefaultProviderId: String?,
 )
 
 /**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index b8b7ac3..98bd020 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -323,12 +323,15 @@
             bottomPadding = 0.dp,
         )
     }) {
+        var isFirstSection = true
         // For username
         items(sortedUserNameToCredentialEntryList) { item ->
             PerUserNameCredentials(
                 perUserNameCredentialEntryList = item,
                 onEntrySelected = onEntrySelected,
+                isFirstSection = isFirstSection,
             )
+            isFirstSection = false
         }
         // Locked password manager
         if (authenticationEntryList.isNotEmpty()) {
@@ -336,7 +339,9 @@
                 LockedCredentials(
                     authenticationEntryList = authenticationEntryList,
                     onEntrySelected = onEntrySelected,
+                    isFirstSection = isFirstSection,
                 )
+                isFirstSection = false
             }
         }
         // From another device
@@ -346,15 +351,19 @@
                 RemoteEntryCard(
                     remoteEntry = remoteEntry,
                     onEntrySelected = onEntrySelected,
+                    isFirstSection = isFirstSection,
                 )
+                isFirstSection = false
             }
         }
         // Manage sign-ins (action chips)
         item {
             ActionChips(
                 providerInfoList = providerInfoList,
-                onEntrySelected = onEntrySelected
+                onEntrySelected = onEntrySelected,
+                isFirstSection = isFirstSection,
             )
+            isFirstSection = false
         }
     }
     onLog(GetCredentialEvent.CREDMAN_GET_CRED_ALL_SIGN_IN_OPTION_CARD)
@@ -367,6 +376,7 @@
 fun ActionChips(
     providerInfoList: List<ProviderInfo>,
     onEntrySelected: (BaseEntry) -> Unit,
+    isFirstSection: Boolean,
 ) {
     val actionChips = providerInfoList.flatMap { it.actionEntryList }
     if (actionChips.isEmpty()) {
@@ -374,7 +384,8 @@
     }
 
     CredentialListSectionHeader(
-        text = stringResource(R.string.get_dialog_heading_manage_sign_ins)
+        text = stringResource(R.string.get_dialog_heading_manage_sign_ins),
+        isFirstSection = isFirstSection,
     )
     CredentialContainerCard {
         Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
@@ -389,9 +400,11 @@
 fun RemoteEntryCard(
     remoteEntry: RemoteEntryInfo,
     onEntrySelected: (BaseEntry) -> Unit,
+    isFirstSection: Boolean,
 ) {
     CredentialListSectionHeader(
-        text = stringResource(R.string.get_dialog_heading_from_another_device)
+        text = stringResource(R.string.get_dialog_heading_from_another_device),
+        isFirstSection = isFirstSection,
     )
     CredentialContainerCard {
         Column(
@@ -413,9 +426,11 @@
 fun LockedCredentials(
     authenticationEntryList: List<AuthenticationEntryInfo>,
     onEntrySelected: (BaseEntry) -> Unit,
+    isFirstSection: Boolean,
 ) {
     CredentialListSectionHeader(
-        text = stringResource(R.string.get_dialog_heading_locked_password_managers)
+        text = stringResource(R.string.get_dialog_heading_locked_password_managers),
+        isFirstSection = isFirstSection,
     )
     CredentialContainerCard {
         Column(
@@ -433,11 +448,13 @@
 fun PerUserNameCredentials(
     perUserNameCredentialEntryList: PerUserNameCredentialEntryList,
     onEntrySelected: (BaseEntry) -> Unit,
+    isFirstSection: Boolean,
 ) {
     CredentialListSectionHeader(
         text = stringResource(
             R.string.get_dialog_heading_for_username, perUserNameCredentialEntryList.userName
-        )
+        ),
+        isFirstSection = isFirstSection,
     )
     CredentialContainerCard {
         Column(
diff --git a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
index 8cda376..bf24c86 100644
--- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
+++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
@@ -59,32 +59,36 @@
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
         TextView title = holder.itemView.findViewById(android.R.id.title);
-        if (!TextUtils.isEmpty(mContentDescription)) {
+        if (title != null && !TextUtils.isEmpty(mContentDescription)) {
             title.setContentDescription(mContentDescription);
         }
 
         TextView learnMore = holder.itemView.findViewById(R.id.settingslib_learn_more);
-        if (learnMore != null && mLearnMoreListener != null) {
-            learnMore.setVisibility(View.VISIBLE);
-            if (TextUtils.isEmpty(mLearnMoreText)) {
-                mLearnMoreText = learnMore.getText();
+        if (learnMore != null) {
+            if (mLearnMoreListener != null) {
+                learnMore.setVisibility(View.VISIBLE);
+                if (TextUtils.isEmpty(mLearnMoreText)) {
+                    mLearnMoreText = learnMore.getText();
+                } else {
+                    learnMore.setText(mLearnMoreText);
+                }
+                SpannableString learnMoreText = new SpannableString(mLearnMoreText);
+                if (mLearnMoreSpan != null) {
+                    learnMoreText.removeSpan(mLearnMoreSpan);
+                }
+                mLearnMoreSpan = new FooterLearnMoreSpan(mLearnMoreListener);
+                learnMoreText.setSpan(mLearnMoreSpan, 0,
+                        learnMoreText.length(), 0);
+                learnMore.setText(learnMoreText);
             } else {
-                learnMore.setText(mLearnMoreText);
+                learnMore.setVisibility(View.GONE);
             }
-            SpannableString learnMoreText = new SpannableString(mLearnMoreText);
-            if (mLearnMoreSpan != null) {
-                learnMoreText.removeSpan(mLearnMoreSpan);
-            }
-            mLearnMoreSpan = new FooterLearnMoreSpan(mLearnMoreListener);
-            learnMoreText.setSpan(mLearnMoreSpan, 0,
-                    learnMoreText.length(), 0);
-            learnMore.setText(learnMoreText);
-        } else {
-            learnMore.setVisibility(View.GONE);
         }
 
         View icon = holder.itemView.findViewById(R.id.icon_frame);
-        icon.setVisibility(mIconVisibility);
+        if (icon != null) {
+            icon.setVisibility(mIconVisibility);
+        }
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index e22f3f0..5fbb4c3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -37,8 +37,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -287,16 +285,7 @@
             return defaultValue;
         }
 
-        try {
-            Method method = mService.getClass().getDeclaredMethod("getDeviceSideInternal",
-                    BluetoothDevice.class);
-            method.setAccessible(true);
-            return (int) method.invoke(mService, device);
-        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
-            Log.e(TAG, "fail to get getDeviceSideInternal\n" + e.toString() + "\n"
-                    + Log.getStackTraceString(new Throwable()));
-            return defaultValue;
-        }
+        return mService.getDeviceSide(device);
     }
 
     /**
@@ -313,17 +302,7 @@
             return defaultValue;
         }
 
-        try {
-            Method method = mService.getClass().getDeclaredMethod("getDeviceModeInternal",
-                    BluetoothDevice.class);
-            method.setAccessible(true);
-            return (int) method.invoke(mService, device);
-        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
-            Log.e(TAG, "fail to get getDeviceModeInternal\n" + e.toString() + "\n"
-                    + Log.getStackTraceString(new Throwable()));
-
-            return defaultValue;
-        }
+        return mService.getDeviceMode(device);
     }
 
     public String toString() {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
index 55125c5..049c90e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
@@ -18,6 +18,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
 import android.content.Context;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -87,4 +90,52 @@
 
         assertThat(mFooterPreference.mIconVisibility).isEqualTo(View.GONE);
     }
+
+    @Test
+    public void onBindViewHolder_whenTitleIsNull_shouldNotRaiseNpe() {
+        PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
+                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
+        when(viewHolder.findViewById(R.id.title)).thenReturn(null);
+
+        Throwable actualThrowable = null;
+        try {
+            mFooterPreference.onBindViewHolder(viewHolder);
+        } catch (Throwable throwable) {
+            actualThrowable = throwable;
+        }
+
+        assertThat(actualThrowable).isNull();
+    }
+
+    @Test
+    public void onBindViewHolder_whenLearnMoreIsNull_shouldNotRaiseNpe() {
+        PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
+                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
+        when(viewHolder.findViewById(R.id.settingslib_learn_more)).thenReturn(null);
+
+        Throwable actualThrowable = null;
+        try {
+            mFooterPreference.onBindViewHolder(viewHolder);
+        } catch (Throwable throwable) {
+            actualThrowable = throwable;
+        }
+
+        assertThat(actualThrowable).isNull();
+    }
+
+    @Test
+    public void onBindViewHolder_whenIconFrameIsNull_shouldNotRaiseNpe() {
+        PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
+                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
+        when(viewHolder.findViewById(R.id.icon_frame)).thenReturn(null);
+
+        Throwable actualThrowable = null;
+        try {
+            mFooterPreference.onBindViewHolder(viewHolder);
+        } catch (Throwable throwable) {
+            actualThrowable = throwable;
+        }
+
+        assertThat(actualThrowable).isNull();
+    }
 }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index fedfb43..78d93bd 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -58,7 +58,6 @@
     <uses-permission android:name="android.permission.ACCEPT_HANDOVER" />
     <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
     <uses-permission android:name="android.permission.BODY_SENSORS" />
-    <uses-permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE" />
     <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
     <uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
     <uses-permission android:name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index dabb578..a00f401 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -984,7 +984,13 @@
             android:exported="true"
             android:excludeFromRecents="true"
             android:resizeableActivity="false"
-            android:theme="@android:style/Theme.NoDisplay" />
+            android:theme="@android:style/Theme.NoDisplay" >
+
+            <intent-filter>
+                <action android:name="com.android.systemui.action.LAUNCH_NOTE_TASK"/>
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
 
         <!-- LaunchNoteTaskManagedProfileProxyActivity MUST NOT be exported because it allows caller
              to specify an Android user when launching the default notes app. -->
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 322fc77..05630e7 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -199,6 +199,9 @@
      */
     val hasCustomPositionUpdatedAnimation: Boolean = false,
 
+    /** Transition to AOD should move smartspace like large clock instead of small clock */
+    val useAlternateSmartspaceAODTransition: Boolean = false,
+
     /** True if the clock will react to tone changes in the seed color. */
     val isReactiveToTone: Boolean = true,
 )
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
index f71c137..2dd146c5 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
@@ -47,6 +47,7 @@
             }
     }
 
+    // Values for WeatherStateIcon must stay in sync with go/g3-WeatherStateIcon
     enum class WeatherStateIcon(val id: Int) {
         UNKNOWN_ICON(0),
 
diff --git a/packages/SystemUI/res/drawable/chipbar_background.xml b/packages/SystemUI/res/drawable/chipbar_background.xml
index 5722177..7530f5b 100644
--- a/packages/SystemUI/res/drawable/chipbar_background.xml
+++ b/packages/SystemUI/res/drawable/chipbar_background.xml
@@ -17,6 +17,6 @@
 <shape
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-    <solid android:color="?androidprv:attr/colorAccentSecondary" />
+    <solid android:color="?androidprv:attr/materialColorSecondaryFixed" />
     <corners android:radius="32dp" />
 </shape>
diff --git a/packages/SystemUI/res/drawable/chipbar_end_button_background.xml b/packages/SystemUI/res/drawable/chipbar_end_button_background.xml
index 80c7207..a3832ee 100644
--- a/packages/SystemUI/res/drawable/chipbar_end_button_background.xml
+++ b/packages/SystemUI/res/drawable/chipbar_end_button_background.xml
@@ -20,7 +20,7 @@
     android:color="?android:textColorPrimary">
     <item android:id="@android:id/background">
         <shape>
-            <solid android:color="@android:color/system_accent1_200"/>
+            <solid android:color="?androidprv:attr/materialColorPrimaryFixedDim"/>
             <corners android:radius="24dp" />
         </shape>
     </item>
diff --git a/packages/SystemUI/res/drawable/hearing.xml b/packages/SystemUI/res/drawable/hearing.xml
new file mode 100644
index 0000000..02f5f92
--- /dev/null
+++ b/packages/SystemUI/res/drawable/hearing.xml
@@ -0,0 +1,24 @@
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportWidth="48"
+    android:viewportHeight="48"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M34.4,43.95Q31.55,43.95 29.45,42.4Q27.35,40.85 26.35,38.3Q25.35,35.75 24.375,34.325Q23.4,32.9 20.7,30.75Q17.4,28.1 15.95,25.1Q14.5,22.1 14.5,17.8Q14.5,11.8 18.3,7.975Q22.1,4.15 28.1,4.15Q34,4.15 37.875,7.825Q41.75,11.5 42,17.2H39Q38.75,12.8 35.725,9.975Q32.7,7.15 28.1,7.15Q23.6,7.15 20.55,10.225Q17.5,13.3 17.5,17.8Q17.5,21.4 18.9,24.025Q20.3,26.65 23.55,29.1Q25.5,30.55 26.675,32.25Q27.85,33.95 28.9,36.45Q29.75,38.55 31.125,39.75Q32.5,40.95 34.4,40.95Q36.15,40.95 37.425,39.75Q38.7,38.55 38.95,36.8H41.95Q41.7,39.8 39.55,41.875Q37.4,43.95 34.4,43.95ZM11.95,32.9Q9.1,29.75 7.55,25.825Q6,21.9 6,17.6Q6,13.35 7.475,9.375Q8.95,5.4 11.95,2.35L14.2,4.35Q11.6,7 10.3,10.425Q9,13.85 9,17.6Q9,21.3 10.325,24.725Q11.65,28.15 14.2,30.85ZM28.1,22.45Q26.15,22.45 24.8,21.1Q23.45,19.75 23.45,17.8Q23.45,15.85 24.8,14.45Q26.15,13.05 28.1,13.05Q30.05,13.05 31.45,14.45Q32.85,15.85 32.85,17.8Q32.85,19.75 31.45,21.1Q30.05,22.45 28.1,22.45Z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/chipbar.xml b/packages/SystemUI/res/layout/chipbar.xml
index 762dcdc..8fa975b 100644
--- a/packages/SystemUI/res/layout/chipbar.xml
+++ b/packages/SystemUI/res/layout/chipbar.xml
@@ -49,15 +49,17 @@
             android:alpha="0.0"
             />
 
+        <!-- LINT.IfChange textColor -->
         <TextView
             android:id="@+id/text"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_weight="1"
-            android:textSize="@dimen/chipbar_text_size"
-            android:textColor="@color/chipbar_text_and_icon_color"
+            style="@style/Chipbar.Text"
+            android:textColor="?androidprv:attr/materialColorOnSecondaryFixed"
             android:alpha="0.0"
             />
+        <!-- LINT.ThenChange(systemui.temporarydisplay.chipbar.ChipbarInfo.kt) -->
 
         <!-- At most one of [loading, failure_icon, undo] will be visible at a time. -->
         <ImageView
@@ -66,7 +68,7 @@
             android:layout_height="@dimen/chipbar_end_icon_size"
             android:layout_marginStart="@dimen/chipbar_end_item_start_margin"
             android:src="@drawable/ic_progress_activity"
-            android:tint="@android:color/system_accent2_700"
+            android:tint="?androidprv:attr/materialColorOnSecondaryFixedVariant"
             android:alpha="0.0"
             />
 
@@ -84,9 +86,9 @@
             android:id="@+id/end_button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:textColor="?androidprv:attr/textColorOnAccent"
             android:layout_marginStart="@dimen/chipbar_end_item_start_margin"
-            android:textSize="@dimen/chipbar_text_size"
+            style="@style/Chipbar.Text"
+            android:textColor="?androidprv:attr/materialColorOnPrimaryFixed"
             android:paddingStart="@dimen/chipbar_outer_padding"
             android:paddingEnd="@dimen/chipbar_outer_padding"
             android:paddingTop="@dimen/chipbar_end_button_vertical_padding"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 96e6d4e..cb8c2a7 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -231,9 +231,6 @@
 
     <color name="people_tile_background">@color/material_dynamic_secondary95</color>
 
-    <!-- Chipbar -->
-    <color name="chipbar_text_and_icon_color">@android:color/system_accent2_900</color>
-
     <!-- Internet Dialog -->
     <!-- Material next state on color-->
     <color name="settingslib_state_on_color">@color/settingslib_state_on</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 4db42aa..0eb0a07 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1110,6 +1110,7 @@
     <!-- Chipbar -->
     <!-- (Used for media tap-to-transfer chip for sender device and active unlock) -->
     <dimen name="chipbar_outer_padding">16dp</dimen>
+    <dimen name="chipbar_outer_padding_half">8dp</dimen>
     <dimen name="chipbar_text_size">16sp</dimen>
     <dimen name="chipbar_start_icon_size">24dp</dimen>
     <dimen name="chipbar_end_icon_size">20dp</dimen>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 2098aea..a359d3b 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -70,6 +70,15 @@
         <item name="android:fontWeight">700</item>
     </style>
 
+    <style name="Chipbar" />
+
+    <style name="Chipbar.Text" parent="@*android:style/TextAppearance.DeviceDefault.Notification.Title">
+        <!-- Text size should be kept in sync with the notification conversation header size. (The
+             conversation header doesn't have a defined style, so the size must be copied here.)
+             See notification_template_conversation_header.xml. -->
+        <item name="android:textSize">16sp</item>
+    </style>
+
     <style name="TextAppearance" />
 
     <style name="TextAppearance.QS">
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 0779653..7262a73 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -25,6 +25,7 @@
 import android.text.format.DateFormat
 import android.util.TypedValue
 import android.view.View
+import android.view.View.OnAttachStateChangeListener
 import android.view.ViewTreeObserver
 import android.widget.FrameLayout
 import androidx.annotation.VisibleForTesting
@@ -105,6 +106,24 @@
                 }
                 updateFontSizes()
                 updateTimeListeners()
+                value.smallClock.view.addOnAttachStateChangeListener(
+                    object : OnAttachStateChangeListener {
+                        override fun onViewAttachedToWindow(p0: View?) {
+                            value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+                        }
+
+                        override fun onViewDetachedFromWindow(p0: View?) {
+                        }
+                })
+                value.largeClock.view.addOnAttachStateChangeListener(
+                    object : OnAttachStateChangeListener {
+                        override fun onViewAttachedToWindow(p0: View?) {
+                            value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+                        }
+
+                        override fun onViewDetachedFromWindow(p0: View?) {
+                        }
+                })
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 87a7758..db38d34 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -75,6 +75,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
@@ -115,6 +116,7 @@
     private final SessionTracker mSessionTracker;
     private final Optional<SideFpsController> mSideFpsController;
     private final FalsingA11yDelegate mFalsingA11yDelegate;
+    private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
     private int mTranslationY;
     // Whether the volume keys should be handled by keyguard. If true, then
     // they will be handled here for specific media types such as music, otherwise
@@ -300,6 +302,7 @@
         @Override
         public void onSwipeUp() {
             if (!mUpdateMonitor.isFaceDetectionRunning()) {
+                mKeyguardFaceAuthInteractor.onSwipeUpOnBouncer();
                 boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(
                         FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
                 mKeyguardSecurityCallback.userActivity();
@@ -389,7 +392,8 @@
             FalsingA11yDelegate falsingA11yDelegate,
             TelephonyManager telephonyManager,
             ViewMediatorCallback viewMediatorCallback,
-            AudioManager audioManager
+            AudioManager audioManager,
+            KeyguardFaceAuthInteractor keyguardFaceAuthInteractor
     ) {
         super(view);
         mLockPatternUtils = lockPatternUtils;
@@ -414,6 +418,7 @@
         mTelephonyManager = telephonyManager;
         mViewMediatorCallback = viewMediatorCallback;
         mAudioManager = audioManager;
+        mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index b8e196f..d8e1eb0f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -19,10 +19,12 @@
 import static java.util.Collections.emptySet;
 
 import android.content.Context;
+import android.os.Build;
 import android.os.Trace;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
 import android.widget.GridLayout;
 
 import com.android.systemui.R;
@@ -118,6 +120,16 @@
     }
 
     @Override
+    public ViewPropertyAnimator animate() {
+        if (Build.IS_DEBUGGABLE) {
+            throw new IllegalArgumentException(
+                    "KeyguardStatusView does not support ViewPropertyAnimator. "
+                            + "Use PropertyAnimator instead.");
+        }
+        return super.animate();
+    }
+
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         Trace.beginSection("KeyguardStatusView#onMeasure");
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 6f54988..0cdef4d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -73,7 +73,7 @@
      */
     private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000;
 
-    private static final AnimationProperties CLOCK_ANIMATION_PROPERTIES =
+    public static final AnimationProperties CLOCK_ANIMATION_PROPERTIES =
             new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
 
     private final KeyguardSliceViewController mKeyguardSliceViewController;
@@ -221,16 +221,32 @@
         mView.setImportantForAccessibility(mode);
     }
 
+    @VisibleForTesting
+    void setProperty(AnimatableProperty property, float value, boolean animate) {
+        PropertyAnimator.setProperty(mView, property, value, CLOCK_ANIMATION_PROPERTIES, animate);
+    }
+
     /**
      * Update position of the view with an optional animation
      */
     public void updatePosition(int x, int y, float scale, boolean animate) {
         float oldY = mView.getY();
-        PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, CLOCK_ANIMATION_PROPERTIES,
-                animate);
+        setProperty(AnimatableProperty.Y, y, animate);
 
-        mKeyguardClockSwitchController.updatePosition(x, scale, CLOCK_ANIMATION_PROPERTIES,
-                animate);
+        ClockController clock = mKeyguardClockSwitchController.getClock();
+        if (clock != null && clock.getConfig().getUseAlternateSmartspaceAODTransition()) {
+            // If requested, scale the entire view instead of just the clock view
+            mKeyguardClockSwitchController.updatePosition(x, 1f /* scale */,
+                    CLOCK_ANIMATION_PROPERTIES, animate);
+            setProperty(AnimatableProperty.SCALE_X, scale, animate);
+            setProperty(AnimatableProperty.SCALE_Y, scale, animate);
+        } else {
+            mKeyguardClockSwitchController.updatePosition(x, scale,
+                    CLOCK_ANIMATION_PROPERTIES, animate);
+            setProperty(AnimatableProperty.SCALE_X, 1f, animate);
+            setProperty(AnimatableProperty.SCALE_Y, 1f, animate);
+        }
+
         if (oldY != y) {
             mKeyguardClockSwitchController.updateKeyguardStatusViewOffset();
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 350c4ed..33a8224 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -154,6 +154,15 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.DumpsysTableLogger;
+import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.DetectionStatus;
+import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus;
 import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.WeatherData;
@@ -372,6 +381,8 @@
     private final FingerprintManager mFpm;
     @Nullable
     private final FaceManager mFaceManager;
+    @Nullable
+    private KeyguardFaceAuthInteractor mFaceAuthInteractor;
     private final LockPatternUtils mLockPatternUtils;
     @VisibleForTesting
     @DevicePostureInt
@@ -1165,8 +1176,21 @@
         Trace.endSection();
     }
 
+    /**
+     * @deprecated This is being migrated to use modern architecture, this method is visible purely
+     * for bridging the gap while the migration is active.
+     */
     private void handleFaceAuthFailed() {
         Assert.isMainThread();
+        String reason =
+                mKeyguardBypassController.canBypass() ? "bypass"
+                        : mAlternateBouncerShowing ? "alternateBouncer"
+                                : mPrimaryBouncerFullyShown ? "bouncer"
+                                        : "udfpsFpDown";
+        requestActiveUnlock(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
+                "faceFailure-" + reason);
+
         mLogger.d("onFaceAuthFailed");
         mFaceCancelSignal = null;
         setFaceRunningState(BIOMETRIC_STATE_STOPPED);
@@ -1180,6 +1204,10 @@
                 mContext.getString(R.string.kg_face_not_recognized));
     }
 
+    /**
+     * @deprecated This is being migrated to use modern architecture, this method is visible purely
+     * for bridging the gap while the migration is active.
+     */
     private void handleFaceAcquired(int acquireInfo) {
         Assert.isMainThread();
         mLogger.logFaceAcquired(acquireInfo);
@@ -1189,8 +1217,19 @@
                 cb.onBiometricAcquired(FACE, acquireInfo);
             }
         }
+
+        if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+                acquireInfo)) {
+            requestActiveUnlock(
+                    ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
+                    "faceAcquireInfo-" + acquireInfo);
+        }
     }
 
+    /**
+     * @deprecated This is being migrated to use modern architecture, this method is visible purely
+     * for bridging the gap while the migration is active.
+     */
     private void handleFaceAuthenticated(int authUserId, boolean isStrongBiometric) {
         Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated");
         try {
@@ -1203,7 +1242,7 @@
                 mLogger.logFaceAuthForWrongUser(authUserId);
                 return;
             }
-            if (isFaceDisabled(userId)) {
+            if (!isFaceAuthInteractorEnabled() && isFaceDisabled(userId)) {
                 mLogger.logFaceAuthDisabledForUser(userId);
                 return;
             }
@@ -1215,6 +1254,10 @@
         Trace.endSection();
     }
 
+    /**
+     * @deprecated This is being migrated to use modern architecture, this method is visible purely
+     * for bridging the gap while the migration is active.
+     */
     private void handleFaceHelp(int msgId, String helpString) {
         Assert.isMainThread();
         mLogger.logFaceAuthHelpMsg(msgId, helpString);
@@ -1226,22 +1269,10 @@
         }
     }
 
-    private final Runnable mRetryFaceAuthentication = new Runnable() {
-        @Override
-        public void run() {
-            mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount);
-            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
-                    FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE);
-        }
-    };
-
-    private void onFaceCancelNotReceived() {
-        mLogger.e("Face cancellation not received, transitioning to STOPPED");
-        mFaceRunningState = BIOMETRIC_STATE_STOPPED;
-        KeyguardUpdateMonitor.this.updateFaceListeningState(BIOMETRIC_ACTION_STOP,
-                FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED);
-    }
-
+    /**
+     * @deprecated This is being migrated to use modern architecture, this method is visible purely
+     * for bridging the gap while the migration is active.
+     */
     private void handleFaceError(int msgId, final String originalErrMsg) {
         Assert.isMainThread();
         String errString = originalErrMsg;
@@ -1299,6 +1330,28 @@
         if (lockedOutStateChanged) {
             notifyLockedOutStateChanged(FACE);
         }
+
+        if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceError(msgId)) {
+            requestActiveUnlock(
+                    ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
+                    "faceError-" + msgId);
+        }
+    }
+
+    private final Runnable mRetryFaceAuthentication = new Runnable() {
+        @Override
+        public void run() {
+            mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount);
+            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
+                    FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE);
+        }
+    };
+
+    private void onFaceCancelNotReceived() {
+        mLogger.e("Face cancellation not received, transitioning to STOPPED");
+        mFaceRunningState = BIOMETRIC_STATE_STOPPED;
+        KeyguardUpdateMonitor.this.updateFaceListeningState(BIOMETRIC_ACTION_STOP,
+                FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED);
     }
 
     private void handleFaceLockoutReset(@LockoutMode int mode) {
@@ -1343,10 +1396,61 @@
         return mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
     }
 
+    /**
+     * @deprecated This is being migrated to use modern architecture.
+     */
+    @Deprecated
     public boolean isFaceDetectionRunning() {
+        if (isFaceAuthInteractorEnabled()) {
+            return getFaceAuthInteractor().isRunning();
+        }
         return mFaceRunningState == BIOMETRIC_STATE_RUNNING;
     }
 
+    private boolean isFaceAuthInteractorEnabled() {
+        return mFaceAuthInteractor != null && mFaceAuthInteractor.isEnabled();
+    }
+
+    private @Nullable KeyguardFaceAuthInteractor getFaceAuthInteractor() {
+        return mFaceAuthInteractor;
+    }
+
+    /**
+     * Set the face auth interactor that should be used for initiating face authentication.
+     */
+    public void setFaceAuthInteractor(@Nullable KeyguardFaceAuthInteractor faceAuthInteractor) {
+        mFaceAuthInteractor = faceAuthInteractor;
+        mFaceAuthInteractor.registerListener(mFaceAuthenticationListener);
+    }
+
+    private FaceAuthenticationListener mFaceAuthenticationListener =
+            new FaceAuthenticationListener() {
+                @Override
+                public void onAuthenticationStatusChanged(@NonNull AuthenticationStatus status) {
+                    if (status instanceof AcquiredAuthenticationStatus) {
+                        handleFaceAcquired(
+                                ((AcquiredAuthenticationStatus) status).getAcquiredInfo());
+                    } else if (status instanceof ErrorAuthenticationStatus) {
+                        ErrorAuthenticationStatus error = (ErrorAuthenticationStatus) status;
+                        handleFaceError(error.getMsgId(), error.getMsg());
+                    } else if (status instanceof FailedAuthenticationStatus) {
+                        handleFaceAuthFailed();
+                    } else if (status instanceof HelpAuthenticationStatus) {
+                        HelpAuthenticationStatus helpMsg = (HelpAuthenticationStatus) status;
+                        handleFaceHelp(helpMsg.getMsgId(), helpMsg.getMsg());
+                    } else if (status instanceof SuccessAuthenticationStatus) {
+                        FaceManager.AuthenticationResult result =
+                                ((SuccessAuthenticationStatus) status).getSuccessResult();
+                        handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());
+                    }
+                }
+
+                @Override
+                public void onDetectionStatusChanged(@NonNull DetectionStatus status) {
+                    handleFaceAuthenticated(status.getUserId(), status.isStrongBiometric());
+                }
+            };
+
     private boolean isTrustDisabled() {
         // Don't allow trust agent if device is secured with a SIM PIN. This is here
         // mainly because there's no other way to prompt the user to enter their SIM PIN
@@ -1360,6 +1464,10 @@
                 || isSimPinSecure();
     }
 
+    /**
+     * @deprecated This method is not needed anymore with the new face auth system.
+     */
+    @Deprecated
     private boolean isFaceDisabled(int userId) {
         // TODO(b/140035044)
         return whitelistIpcs(() ->
@@ -1371,7 +1479,10 @@
     /**
      * @return whether the current user has been authenticated with face. This may be true
      * on the lockscreen if the user doesn't have bypass enabled.
+     *
+     * @deprecated This is being migrated to use modern architecture.
      */
+    @Deprecated
     public boolean getIsFaceAuthenticated() {
         boolean faceAuthenticated = false;
         BiometricAuthenticated bioFaceAuthenticated = mUserFaceAuthenticated.get(getCurrentUser());
@@ -1619,6 +1730,9 @@
     void setAssistantVisible(boolean assistantVisible) {
         mAssistantVisible = assistantVisible;
         mLogger.logAssistantVisible(mAssistantVisible);
+        if (isFaceAuthInteractorEnabled()) {
+            mFaceAuthInteractor.onAssistantTriggeredOnLockScreen();
+        }
         updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
                 FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED);
         if (mAssistantVisible) {
@@ -1832,54 +1946,27 @@
 
                 @Override
                 public void onAuthenticationFailed() {
-                        String reason =
-                                mKeyguardBypassController.canBypass() ? "bypass"
-                                        : mAlternateBouncerShowing ? "alternateBouncer"
-                                                : mPrimaryBouncerFullyShown ? "bouncer"
-                                                        : "udfpsFpDown";
-                        requestActiveUnlock(
-                                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
-                                "faceFailure-" + reason);
-
                     handleFaceAuthFailed();
                 }
 
                 @Override
                 public void onAuthenticationSucceeded(FaceManager.AuthenticationResult result) {
-                    Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
                     handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());
-                    Trace.endSection();
                 }
 
                 @Override
                 public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
-                    if (mFaceAcquiredInfoIgnoreList.contains(helpMsgId)) {
-                        return;
-                    }
                     handleFaceHelp(helpMsgId, helpString.toString());
                 }
 
                 @Override
                 public void onAuthenticationError(int errMsgId, CharSequence errString) {
                     handleFaceError(errMsgId, errString.toString());
-
-                    if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceError(errMsgId)) {
-                        requestActiveUnlock(
-                                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
-                                "faceError-" + errMsgId);
-                    }
                 }
 
                 @Override
                 public void onAuthenticationAcquired(int acquireInfo) {
                     handleFaceAcquired(acquireInfo);
-
-                    if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
-                            acquireInfo)) {
-                        requestActiveUnlock(
-                                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
-                                "faceAcquireInfo-" + acquireInfo);
-                    }
                 }
     };
 
@@ -2628,7 +2715,9 @@
      * @param reason One of the reasons {@link FaceAuthApiRequestReason} on why this API is being
      * invoked.
      * @return current face auth detection state, true if it is running.
+     * @deprecated This is being migrated to use modern architecture.
      */
+    @Deprecated
     public boolean requestFaceAuth(@FaceAuthApiRequestReason String reason) {
         mLogger.logFaceAuthRequested(reason);
         updateFaceListeningState(BIOMETRIC_ACTION_START, apiRequestReasonToUiEvent(reason));
@@ -2643,6 +2732,7 @@
     }
 
     private void updateFaceListeningState(int action, @NonNull FaceAuthUiEvent faceAuthUiEvent) {
+        if (isFaceAuthInteractorEnabled()) return;
         // If this message exists, we should not authenticate again until this message is
         // consumed by the handler
         if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) {
@@ -3154,6 +3244,9 @@
     }
 
     public boolean isFaceLockedOut() {
+        if (isFaceAuthInteractorEnabled()) {
+            return getFaceAuthInteractor().isLockedOut();
+        }
         return mFaceLockedOutPermanent;
     }
 
@@ -3202,13 +3295,23 @@
         return mIsUnlockWithFingerprintPossible.getOrDefault(userId, false);
     }
 
+    /**
+     * @deprecated This is being migrated to use modern architecture.
+     */
+    @Deprecated
     private boolean isUnlockWithFacePossible(int userId) {
+        if (isFaceAuthInteractorEnabled()) {
+            return getFaceAuthInteractor().canFaceAuthRun();
+        }
         return isFaceAuthEnabledForUser(userId) && !isFaceDisabled(userId);
     }
 
     /**
      * If face hardware is available, user has enrolled and enabled auth via setting.
+     *
+     * @deprecated This is being migrated to use modern architecture.
      */
+    @Deprecated
     public boolean isFaceAuthEnabledForUser(int userId) {
         // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
         updateFaceEnrolled(userId);
@@ -3232,6 +3335,7 @@
     }
 
     private void stopListeningForFace(@NonNull FaceAuthUiEvent faceAuthUiEvent) {
+        if (isFaceAuthInteractorEnabled()) return;
         mLogger.v("stopListeningForFace()");
         mLogger.logStoppedListeningForFace(mFaceRunningState, faceAuthUiEvent.getReason());
         mUiEventLogger.log(faceAuthUiEvent, getKeyguardSessionId());
@@ -4098,6 +4202,9 @@
         mStatusBarStateController.removeCallback(mStatusBarStateControllerListener);
         mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener);
         mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionListener);
+        if (isFaceAuthInteractorEnabled()) {
+            mFaceAuthInteractor.unregisterListener(mFaceAuthenticationListener);
+        }
 
         if (mDeviceProvisionedObserver != null) {
             mContext.getContentResolver().unregisterContentObserver(mDeviceProvisionedObserver);
@@ -4127,6 +4234,7 @@
         pw.println("  getUserHasTrust()=" + getUserHasTrust(getCurrentUser()));
         pw.println("  getUserUnlockedWithBiometric()="
                 + getUserUnlockedWithBiometric(getCurrentUser()));
+        pw.println("  isFaceAuthInteractorEnabled: " + isFaceAuthInteractorEnabled());
         pw.println("  SIM States:");
         for (SimData data : mSimDatas.values()) {
             pw.println("    " + data.toString());
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index a678edc..651c979 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -18,8 +18,8 @@
 
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 
+import android.util.Property;
 import android.view.View;
-import android.view.ViewPropertyAnimator;
 
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.log.LogBuffer;
@@ -34,6 +34,8 @@
 
 import com.google.errorprone.annotations.CompileTimeConstant;
 
+import java.util.function.Consumer;
+
 /**
  * Helper class for updating visibility of keyguard views based on keyguard and status bar state.
  * This logic is shared by both the keyguard status view and the keyguard user switcher.
@@ -83,47 +85,49 @@
             boolean keyguardFadingAway,
             boolean goingToFullShade,
             int oldStatusBarState) {
-        mView.animate().cancel();
+        PropertyAnimator.cancelAnimation(mView, AnimatableProperty.ALPHA);
         boolean isOccluded = mKeyguardStateController.isOccluded();
         mKeyguardViewVisibilityAnimating = false;
 
         if ((!keyguardFadingAway && oldStatusBarState == KEYGUARD
                 && statusBarState != KEYGUARD) || goingToFullShade) {
             mKeyguardViewVisibilityAnimating = true;
-            mView.animate()
-                    .alpha(0f)
-                    .setStartDelay(0)
-                    .setDuration(160)
-                    .setInterpolator(Interpolators.ALPHA_OUT)
-                    .withEndAction(
-                            mAnimateKeyguardStatusViewGoneEndRunnable);
+
+            AnimationProperties animProps = new AnimationProperties()
+                    .setCustomInterpolator(View.ALPHA, Interpolators.ALPHA_OUT)
+                    .setAnimationEndAction(mSetGoneEndAction);
             if (keyguardFadingAway) {
-                mView.animate()
-                        .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
-                        .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
-                        .start();
+                animProps
+                        .setDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
+                        .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration());
                 log("goingToFullShade && keyguardFadingAway");
             } else {
+                animProps.setDelay(0).setDuration(160);
                 log("goingToFullShade && !keyguardFadingAway");
             }
+            PropertyAnimator.setProperty(
+                    mView, AnimatableProperty.ALPHA, 0f, animProps, true /* animate */);
         } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
             mView.setVisibility(View.VISIBLE);
             mKeyguardViewVisibilityAnimating = true;
             mView.setAlpha(0f);
-            mView.animate()
-                    .alpha(1f)
-                    .setStartDelay(0)
-                    .setDuration(320)
-                    .setInterpolator(Interpolators.ALPHA_IN)
-                    .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
+            PropertyAnimator.setProperty(
+                    mView, AnimatableProperty.ALPHA, 1f,
+                    new AnimationProperties()
+                            .setDelay(0)
+                            .setDuration(320)
+                            .setCustomInterpolator(View.ALPHA, Interpolators.ALPHA_IN)
+                            .setAnimationEndAction(
+                                    property -> mSetVisibleEndRunnable.run()),
+                    true /* animate */);
             log("keyguardFadingAway transition w/ Y Aniamtion");
         } else if (statusBarState == KEYGUARD) {
             if (keyguardFadingAway) {
                 mKeyguardViewVisibilityAnimating = true;
-                ViewPropertyAnimator animator = mView.animate()
-                        .alpha(0)
-                        .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
-                        .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable);
+                AnimationProperties animProps = new AnimationProperties()
+                        .setDelay(0)
+                        .setCustomInterpolator(View.ALPHA, Interpolators.FAST_OUT_LINEAR_IN)
+                        .setAnimationEndAction(mSetInvisibleEndAction);
                 if (mAnimateYPos) {
                     float target = mView.getY() - mView.getHeight() * 0.05f;
                     int delay = 0;
@@ -135,13 +139,16 @@
                     PropertyAnimator.setProperty(mView, AnimatableProperty.Y, target,
                             mAnimationProperties,
                             true /* animate */);
-                    animator.setDuration(duration)
-                            .setStartDelay(delay);
+                    animProps.setDuration(duration)
+                            .setDelay(delay);
                     log("keyguardFadingAway transition w/ Y Aniamtion");
                 } else {
                     log("keyguardFadingAway transition w/o Y Animation");
                 }
-                animator.start();
+                PropertyAnimator.setProperty(
+                        mView, AnimatableProperty.ALPHA, 0f,
+                        animProps,
+                        true /* animate */);
             } else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) {
                 log("ScreenOff transition");
                 mKeyguardViewVisibilityAnimating = true;
@@ -149,7 +156,7 @@
                 // Ask the screen off animation controller to animate the keyguard visibility for us
                 // since it may need to be cancelled due to keyguard lifecycle events.
                 mScreenOffAnimationController.animateInKeyguard(
-                        mView, mAnimateKeyguardStatusViewVisibleEndRunnable);
+                        mView, mSetVisibleEndRunnable);
             } else {
                 log("Direct set Visibility to VISIBLE");
                 mView.setVisibility(View.VISIBLE);
@@ -163,19 +170,25 @@
         mLastOccludedState = isOccluded;
     }
 
-    private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> {
-        mKeyguardViewVisibilityAnimating = false;
-        mView.setVisibility(View.INVISIBLE);
-        log("Callback Set Visibility to INVISIBLE");
+    private final Consumer<Property> mSetInvisibleEndAction = new Consumer<>() {
+        @Override
+        public void accept(Property property) {
+            mKeyguardViewVisibilityAnimating = false;
+            mView.setVisibility(View.INVISIBLE);
+            log("Callback Set Visibility to INVISIBLE");
+        }
     };
 
-    private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> {
-        mKeyguardViewVisibilityAnimating = false;
-        mView.setVisibility(View.GONE);
-        log("CallbackSet Visibility to GONE");
+    private final Consumer<Property> mSetGoneEndAction = new Consumer<>() {
+        @Override
+        public void accept(Property property) {
+            mKeyguardViewVisibilityAnimating = false;
+            mView.setVisibility(View.GONE);
+            log("CallbackSet Visibility to GONE");
+        }
     };
 
-    private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> {
+    private final Runnable mSetVisibleEndRunnable = () -> {
         mKeyguardViewVisibilityAnimating = false;
         mView.setVisibility(View.VISIBLE);
         log("Callback Set Visibility to VISIBLE");
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
index d15a2af..6c62a39 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -39,7 +39,7 @@
     private fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) =
         mainExecutor.execute {
             action?.let {
-                if (event.tracking || event.expanded) {
+                if (event.tracking || (event.expanded && event.fraction > 0)) {
                     Log.v(TAG, "Detected panel interaction, event: $event")
                     it.onPanelInteraction.run()
                     disable()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index aabdafb..7a23759 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -83,6 +83,7 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
@@ -151,6 +152,7 @@
     @NonNull private final DumpManager mDumpManager;
     @NonNull private final SystemUIDialogManager mDialogManager;
     @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
     @NonNull private final VibratorHelper mVibrator;
     @NonNull private final FeatureFlags mFeatureFlags;
     @NonNull private final FalsingManager mFalsingManager;
@@ -818,7 +820,8 @@
             @NonNull AlternateBouncerInteractor alternateBouncerInteractor,
             @NonNull SecureSettings secureSettings,
             @NonNull InputManager inputManager,
-            @NonNull UdfpsUtils udfpsUtils) {
+            @NonNull UdfpsUtils udfpsUtils,
+            @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor) {
         mContext = context;
         mExecution = execution;
         mVibrator = vibrator;
@@ -882,6 +885,7 @@
                     }
                     return Unit.INSTANCE;
                 });
+        mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
 
         final UdfpsOverlayController mUdfpsOverlayController = new UdfpsOverlayController();
         mFingerprintManager.setUdfpsOverlayController(mUdfpsOverlayController);
@@ -1141,6 +1145,7 @@
         if (!mOnFingerDown) {
             playStartHaptic();
 
+            mKeyguardFaceAuthInteractor.onUdfpsSensorTouched();
             if (!mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
                 mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
index 6a6c3eb..0869351 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
@@ -16,10 +16,10 @@
 
 package com.android.systemui.common.shared.model
 
-import androidx.annotation.ColorRes
+import androidx.annotation.AttrRes
 
 /** Models an icon with a specific tint. */
 data class TintedIcon(
     val icon: Icon,
-    @ColorRes val tint: Int?,
+    @AttrRes val tint: Int?,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
index bcc5932..5c5723f 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.common.ui.binder
 
 import android.widget.ImageView
+import com.android.settingslib.Utils
 import com.android.systemui.common.shared.model.TintedIcon
 
 object TintedIconViewBinder {
@@ -33,7 +34,7 @@
         IconViewBinder.bind(tintedIcon.icon, view)
         view.imageTintList =
             if (tintedIcon.tint != null) {
-                view.resources.getColorStateList(tintedIcon.tint, view.context.theme)
+                Utils.getColorAttr(view.context, tintedIcon.tint)
             } else {
                 null
             }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
index 7f44463..aca621b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
@@ -41,6 +41,7 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -195,7 +196,14 @@
          * Called by the monitor when this session is removed.
          */
         private void onRemoved() {
-            mCallbacks.forEach(callback -> callback.onRemoved());
+            mEventListeners.clear();
+            mGestureListeners.clear();
+            final Iterator<Callback> iter = mCallbacks.iterator();
+            while (iter.hasNext()) {
+                final Callback callback = iter.next();
+                callback.onRemoved();
+                iter.remove();
+            }
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 012c8cf..f28aead 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -108,11 +108,6 @@
     val NOTIFICATION_SHELF_REFACTOR =
         unreleasedFlag(271161129, "notification_shelf_refactor")
 
-    // TODO(b/263414400): Tracking Bug
-    @JvmField
-    val NOTIFICATION_ANIMATE_BIG_PICTURE =
-        releasedFlag(120, "notification_animate_big_picture")
-
     @JvmField
     val ANIMATED_NOTIFICATION_SHADE_INSETS =
         releasedFlag(270682168, "animated_notification_shade_insets")
@@ -605,9 +600,6 @@
     // TODO(b/254512507): Tracking Bug
     val CHOOSER_UNBUNDLED = releasedFlag(1500, "chooser_unbundled")
 
-    // TODO(b/266983432) Tracking Bug
-    val SHARESHEET_CUSTOM_ACTIONS = releasedFlag(1501, "sharesheet_custom_actions")
-
     // TODO(b/266982749) Tracking Bug
     val SHARESHEET_RESELECTION_ACTION = releasedFlag(1502, "sharesheet_reselection_action")
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt
new file mode 100644
index 0000000..a44df7e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt
@@ -0,0 +1,35 @@
+/*
+ *   Copyright (C) 2023 The Android Open Source Project
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ */
+
+package com.android.systemui.keyguard.dagger
+
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.keyguard.domain.interactor.NoopKeyguardFaceAuthInteractor
+import dagger.Binds
+import dagger.Module
+
+/**
+ * Module that provides bindings for face auth classes that are injected into SysUI components that
+ * are used across different SysUI variants, where face auth is not supported.
+ *
+ * Some variants that do not support face authentication can install this module to provide a no-op
+ * implementation of the interactor.
+ */
+@Module
+interface KeyguardFaceAuthNotSupportedModule {
+    @Binds
+    fun keyguardFaceAuthInteractor(impl: NoopKeyguardFaceAuthInteractor): KeyguardFaceAuthInteractor
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 0abce82..c4fc883 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -44,6 +44,8 @@
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.log.SessionTracker
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.user.data.repository.UserRepository
 import java.io.PrintWriter
@@ -78,7 +80,7 @@
     val isAuthenticated: Flow<Boolean>
 
     /** Whether face auth can run at this point. */
-    val canRunFaceAuth: Flow<Boolean>
+    val canRunFaceAuth: StateFlow<Boolean>
 
     /** Provide the current status of face authentication. */
     val authenticationStatus: Flow<AuthenticationStatus>
@@ -87,10 +89,10 @@
     val detectionStatus: Flow<DetectionStatus>
 
     /** Current state of whether face authentication is locked out or not. */
-    val isLockedOut: Flow<Boolean>
+    val isLockedOut: StateFlow<Boolean>
 
     /** Current state of whether face authentication is running. */
-    val isAuthRunning: Flow<Boolean>
+    val isAuthRunning: StateFlow<Boolean>
 
     /** Whether bypass is currently enabled */
     val isBypassEnabled: Flow<Boolean>
@@ -129,6 +131,8 @@
     private val keyguardRepository: KeyguardRepository,
     private val keyguardInteractor: KeyguardInteractor,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
+    @FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
+    @FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
     dumpManager: DumpManager,
 ) : DeviceEntryFaceAuthRepository, Dumpable {
     private var authCancellationSignal: CancellationSignal? = null
@@ -224,17 +228,19 @@
         // Face detection can run only when lockscreen bypass is enabled
         // & detection is supported & biometric unlock is not allowed.
         listOf(
-                canFaceAuthOrDetectRun(),
-                logAndObserve(isBypassEnabled, "isBypassEnabled"),
+                canFaceAuthOrDetectRun(faceDetectLog),
+                logAndObserve(isBypassEnabled, "isBypassEnabled", faceDetectLog),
                 logAndObserve(
                     biometricSettingsRepository.isNonStrongBiometricAllowed.isFalse(),
-                    "nonStrongBiometricIsNotAllowed"
+                    "nonStrongBiometricIsNotAllowed",
+                    faceDetectLog
                 ),
                 // We don't want to run face detect if it's not possible to authenticate with FP
                 // from the bouncer. UDFPS is the only fp sensor type that won't support this.
                 logAndObserve(
                     and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning).isFalse(),
-                    "udfpsAuthIsNotPossibleAnymore"
+                    "udfpsAuthIsNotPossibleAnymore",
+                    faceDetectLog
                 )
             )
             .reduce(::and)
@@ -246,6 +252,7 @@
                     cancelDetection()
                 }
             }
+            .logDiffsForTable(faceDetectLog, "", "canFaceDetectRun", false)
             .launchIn(applicationScope)
     }
 
@@ -254,26 +261,34 @@
             it == BiometricType.UNDER_DISPLAY_FINGERPRINT
         }
 
-    private fun canFaceAuthOrDetectRun(): Flow<Boolean> {
+    private fun canFaceAuthOrDetectRun(tableLogBuffer: TableLogBuffer): Flow<Boolean> {
         return listOf(
-                logAndObserve(biometricSettingsRepository.isFaceEnrolled, "isFaceEnrolled"),
+                logAndObserve(
+                    biometricSettingsRepository.isFaceEnrolled,
+                    "isFaceEnrolled",
+                    tableLogBuffer
+                ),
                 logAndObserve(
                     biometricSettingsRepository.isFaceAuthenticationEnabled,
-                    "isFaceAuthenticationEnabled"
+                    "isFaceAuthenticationEnabled",
+                    tableLogBuffer
                 ),
                 logAndObserve(
                     userRepository.userSwitchingInProgress.isFalse(),
-                    "userSwitchingNotInProgress"
+                    "userSwitchingNotInProgress",
+                    tableLogBuffer
                 ),
                 logAndObserve(
                     keyguardRepository.isKeyguardGoingAway.isFalse(),
-                    "keyguardNotGoingAway"
+                    "keyguardNotGoingAway",
+                    tableLogBuffer
                 ),
                 logAndObserve(
                     keyguardRepository.wakefulness
                         .map { WakefulnessModel.isSleepingOrStartingToSleep(it) }
                         .isFalse(),
-                    "deviceNotSleepingOrNotStartingToSleep"
+                    "deviceNotSleepingOrNotStartingToSleep",
+                    tableLogBuffer
                 ),
                 logAndObserve(
                     combine(
@@ -282,15 +297,18 @@
                     ) { a, b ->
                         !a || b
                     },
-                    "secureCameraNotActiveOrAltBouncerIsShowing"
+                    "secureCameraNotActiveOrAltBouncerIsShowing",
+                    tableLogBuffer
                 ),
                 logAndObserve(
                     biometricSettingsRepository.isFaceAuthSupportedInCurrentPosture,
-                    "isFaceAuthSupportedInCurrentPosture"
+                    "isFaceAuthSupportedInCurrentPosture",
+                    tableLogBuffer
                 ),
                 logAndObserve(
                     biometricSettingsRepository.isCurrentUserInLockdown.isFalse(),
-                    "userHasNotLockedDownDevice"
+                    "userHasNotLockedDownDevice",
+                    tableLogBuffer
                 )
             )
             .reduce(::and)
@@ -299,20 +317,27 @@
     private fun observeFaceAuthGatingChecks() {
         // Face auth can run only if all of the gating conditions are true.
         listOf(
-                canFaceAuthOrDetectRun(),
-                logAndObserve(isLockedOut.isFalse(), "isNotLocked"),
+                canFaceAuthOrDetectRun(faceAuthLog),
+                logAndObserve(isLockedOut.isFalse(), "isNotInLockOutState", faceAuthLog),
                 logAndObserve(
                     deviceEntryFingerprintAuthRepository.isLockedOut.isFalse(),
-                    "fpLockedOut"
+                    "fpLockedOut",
+                    faceAuthLog
                 ),
-                logAndObserve(trustRepository.isCurrentUserTrusted.isFalse(), "currentUserTrusted"),
+                logAndObserve(
+                    trustRepository.isCurrentUserTrusted.isFalse(),
+                    "currentUserTrusted",
+                    faceAuthLog
+                ),
                 logAndObserve(
                     biometricSettingsRepository.isNonStrongBiometricAllowed,
-                    "nonStrongBiometricIsAllowed"
+                    "nonStrongBiometricIsAllowed",
+                    faceAuthLog
                 ),
                 logAndObserve(
                     userRepository.selectedUserInfo.map { it.isPrimary },
-                    "userIsPrimaryUser"
+                    "userIsPrimaryUser",
+                    faceAuthLog
                 ),
             )
             .reduce(::and)
@@ -326,6 +351,7 @@
                     cancel()
                 }
             }
+            .logDiffsForTable(faceAuthLog, "", "canFaceAuthRun", false)
             .launchIn(applicationScope)
     }
 
@@ -340,7 +366,6 @@
 
             override fun onAuthenticationAcquired(acquireInfo: Int) {
                 _authenticationStatus.value = AcquiredAuthenticationStatus(acquireInfo)
-                faceAuthLogger.authenticationAcquired(acquireInfo)
             }
 
             override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
@@ -401,7 +426,7 @@
 
     override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
         if (_isAuthRunning.value) {
-            faceAuthLogger.ignoredFaceAuthTrigger(uiEvent)
+            faceAuthLogger.ignoredFaceAuthTrigger(uiEvent, "face auth is currently running")
             return
         }
 
@@ -438,7 +463,16 @@
                 )
             }
         } else if (fallbackToDetection && canRunDetection.value) {
+            faceAuthLogger.ignoredFaceAuthTrigger(
+                uiEvent,
+                "face auth gating check is false, falling back to detection."
+            )
             detect()
+        } else {
+            faceAuthLogger.ignoredFaceAuthTrigger(
+                uiEvent,
+                "face auth & detect gating check is false"
+            )
         }
     }
 
@@ -467,7 +501,7 @@
     private val currentUserId: Int
         get() = userRepository.getSelectedUserInfo().id
 
-    fun cancelDetection() {
+    private fun cancelDetection() {
         detectCancellationSignal?.cancel()
         detectCancellationSignal = null
     }
@@ -491,10 +525,20 @@
         _isAuthRunning.value = false
     }
 
-    private fun logAndObserve(cond: Flow<Boolean>, loggingContext: String): Flow<Boolean> {
-        return cond.distinctUntilChanged().onEach {
-            faceAuthLogger.observedConditionChanged(it, loggingContext)
-        }
+    private fun logAndObserve(
+        cond: Flow<Boolean>,
+        conditionName: String,
+        logBuffer: TableLogBuffer
+    ): Flow<Boolean> {
+        return cond
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                logBuffer,
+                columnName = conditionName,
+                columnPrefix = "",
+                initialValue = false
+            )
+            .onEach { faceAuthLogger.observedConditionChanged(it, conditionName) }
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceAuthTableLog.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceAuthTableLog.kt
new file mode 100644
index 0000000..6c23032
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceAuthTableLog.kt
@@ -0,0 +1,25 @@
+/*
+ *   Copyright (C) 2023 The Android Open Source Project
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import javax.inject.Qualifier
+
+/** Face auth logs in table format. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class FaceAuthTableLog
diff --git a/core/java/com/android/internal/expresslog/Utils.java b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceDetectTableLog.kt
similarity index 72%
rename from core/java/com/android/internal/expresslog/Utils.java
rename to packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceDetectTableLog.kt
index d82192f..342064f 100644
--- a/core/java/com/android/internal/expresslog/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceDetectTableLog.kt
@@ -14,8 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.internal.expresslog;
+package com.android.systemui.keyguard.data.repository
 
-final class Utils {
-    static native long hashString(String stringToHash);
-}
+import javax.inject.Qualifier
+
+/** Face detect logs in table format. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class FaceDetectTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
index 3c66f24..ef8b401 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
@@ -17,8 +17,17 @@
 
 package com.android.systemui.keyguard.data.repository
 
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.keyguard.domain.interactor.SystemUIKeyguardFaceAuthInteractor
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
 
 @Module
 interface KeyguardFaceAuthModule {
@@ -27,5 +36,31 @@
         impl: DeviceEntryFaceAuthRepositoryImpl
     ): DeviceEntryFaceAuthRepository
 
+    @Binds
+    @IntoMap
+    @ClassKey(SystemUIKeyguardFaceAuthInteractor::class)
+    fun bind(impl: SystemUIKeyguardFaceAuthInteractor): CoreStartable
+
+    @Binds
+    fun keyguardFaceAuthInteractor(
+        impl: SystemUIKeyguardFaceAuthInteractor
+    ): KeyguardFaceAuthInteractor
+
     @Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
+
+    companion object {
+        @Provides
+        @SysUISingleton
+        @FaceAuthTableLog
+        fun provideFaceAuthTableLog(factory: TableLogBufferFactory): TableLogBuffer {
+            return factory.create("FaceAuthTableLog", 100)
+        }
+
+        @Provides
+        @SysUISingleton
+        @FaceDetectTableLog
+        fun provideFaceDetectTableLog(factory: TableLogBufferFactory): TableLogBuffer {
+            return factory.create("FaceDetectTableLog", 100)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
new file mode 100644
index 0000000..06ae11fe8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
@@ -0,0 +1,78 @@
+/*
+ *   Copyright (C) 2023 The Android Open Source Project
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Interactor that exposes API to get the face authentication status and handle any events that can
+ * cause face authentication to run.
+ */
+interface KeyguardFaceAuthInteractor {
+
+    /** Current authentication status */
+    val authenticationStatus: Flow<AuthenticationStatus>
+
+    /** Current detection status */
+    val detectionStatus: Flow<DetectionStatus>
+
+    /** Can face auth be run right now */
+    fun canFaceAuthRun(): Boolean
+
+    /** Whether face auth is currently running or not. */
+    fun isRunning(): Boolean
+
+    /** Whether face auth is in lock out state. */
+    fun isLockedOut(): Boolean
+
+    /**
+     * Register listener for use from code that cannot use [authenticationStatus] or
+     * [detectionStatus]
+     */
+    fun registerListener(listener: FaceAuthenticationListener)
+
+    /** Unregister previously registered listener */
+    fun unregisterListener(listener: FaceAuthenticationListener)
+
+    /** Whether the face auth interactor is enabled or not. */
+    fun isEnabled(): Boolean
+
+    fun onUdfpsSensorTouched()
+    fun onAssistantTriggeredOnLockScreen()
+    fun onDeviceLifted()
+    fun onQsExpansionStared()
+    fun onNotificationPanelClicked()
+    fun onSwipeUpOnBouncer()
+}
+
+/**
+ * Listener that can be registered with the [KeyguardFaceAuthInteractor] to receive updates about
+ * face authentication & detection updates.
+ *
+ * This is present to make it easier for use the new face auth API for code that cannot use
+ * [KeyguardFaceAuthInteractor.authenticationStatus] or [KeyguardFaceAuthInteractor.detectionStatus]
+ * flows.
+ */
+interface FaceAuthenticationListener {
+    /** Receive face authentication status updates */
+    fun onAuthenticationStatusChanged(status: AuthenticationStatus)
+
+    /** Receive status updates whenever face detection runs */
+    fun onDetectionStatusChanged(status: DetectionStatus)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index aabd212..da0ada1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -78,6 +78,14 @@
     val primaryBouncerToGoneTransition: Flow<TransitionStep> =
         repository.transition(PRIMARY_BOUNCER, GONE)
 
+    /** OFF->LOCKSCREEN transition information. */
+    val offToLockscreenTransition: Flow<TransitionStep> =
+        repository.transition(KeyguardState.OFF, LOCKSCREEN)
+
+    /** DOZING->LOCKSCREEN transition information. */
+    val dozingToLockscreenTransition: Flow<TransitionStep> =
+        repository.transition(KeyguardState.DOZING, LOCKSCREEN)
+
     /**
      * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
      * Lockscreen (0f).
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
new file mode 100644
index 0000000..cad40aa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
@@ -0,0 +1,62 @@
+/*
+ *   Copyright (C) 2023 The Android Open Source Project
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+
+/**
+ * Implementation of the interactor that noops all face auth operations.
+ *
+ * This is required for SystemUI variants that do not support face authentication but still inject
+ * other SysUI components that depend on [KeyguardFaceAuthInteractor]
+ */
+@SysUISingleton
+class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInteractor {
+    override val authenticationStatus: Flow<AuthenticationStatus>
+        get() = emptyFlow()
+    override val detectionStatus: Flow<DetectionStatus>
+        get() = emptyFlow()
+
+    override fun canFaceAuthRun(): Boolean = false
+
+    override fun isRunning(): Boolean = false
+
+    override fun isLockedOut(): Boolean = false
+
+    override fun isEnabled() = false
+
+    override fun registerListener(listener: FaceAuthenticationListener) {}
+
+    override fun unregisterListener(listener: FaceAuthenticationListener) {}
+
+    override fun onUdfpsSensorTouched() {}
+
+    override fun onAssistantTriggeredOnLockScreen() {}
+
+    override fun onDeviceLifted() {}
+
+    override fun onQsExpansionStared() {}
+
+    override fun onNotificationPanelClicked() {}
+
+    override fun onSwipeUpOnBouncer() {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
new file mode 100644
index 0000000..20ebb71
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -0,0 +1,200 @@
+/*
+ *   Copyright (C) 2023 The Android Open Source Project
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.log.FaceAuthenticationLogger
+import com.android.systemui.util.kotlin.pairwise
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+
+/**
+ * Encapsulates business logic related face authentication being triggered for device entry from
+ * SystemUI Keyguard.
+ */
+@SysUISingleton
+class SystemUIKeyguardFaceAuthInteractor
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    private val repository: DeviceEntryFaceAuthRepository,
+    private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+    private val alternateBouncerInteractor: AlternateBouncerInteractor,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val featureFlags: FeatureFlags,
+    private val faceAuthenticationLogger: FaceAuthenticationLogger,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+) : CoreStartable, KeyguardFaceAuthInteractor {
+
+    private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
+
+    override fun start() {
+        if (!isEnabled()) {
+            return
+        }
+        // This is required because fingerprint state required for the face auth repository is
+        // backed by KeyguardUpdateMonitor. KeyguardUpdateMonitor constructor accesses the biometric
+        // state which makes lazy injection not an option.
+        keyguardUpdateMonitor.setFaceAuthInteractor(this)
+        observeFaceAuthStateUpdates()
+        faceAuthenticationLogger.interactorStarted()
+        primaryBouncerInteractor.isShowing
+            .whenItFlipsToTrue()
+            .onEach {
+                faceAuthenticationLogger.bouncerVisibilityChanged()
+                runFaceAuth(
+                    FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN,
+                    fallbackToDetect = true
+                )
+            }
+            .launchIn(applicationScope)
+
+        alternateBouncerInteractor.isVisible
+            .whenItFlipsToTrue()
+            .onEach {
+                faceAuthenticationLogger.alternateBouncerVisibilityChanged()
+                runFaceAuth(
+                    FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN,
+                    fallbackToDetect = false
+                )
+            }
+            .launchIn(applicationScope)
+
+        merge(
+                keyguardTransitionInteractor.aodToLockscreenTransition,
+                keyguardTransitionInteractor.offToLockscreenTransition,
+                keyguardTransitionInteractor.dozingToLockscreenTransition
+            )
+            .filter { it.transitionState == TransitionState.STARTED }
+            .onEach {
+                faceAuthenticationLogger.lockscreenBecameVisible(it)
+                runFaceAuth(
+                    FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED,
+                    fallbackToDetect = true
+                )
+            }
+            .launchIn(applicationScope)
+    }
+
+    override fun onSwipeUpOnBouncer() {
+        runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false)
+    }
+
+    override fun onNotificationPanelClicked() {
+        runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, true)
+    }
+
+    override fun onQsExpansionStared() {
+        runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, true)
+    }
+
+    override fun onDeviceLifted() {
+        runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED, true)
+    }
+
+    override fun onAssistantTriggeredOnLockScreen() {
+        runFaceAuth(FaceAuthUiEvent.FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED, true)
+    }
+
+    override fun onUdfpsSensorTouched() {
+        runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN, false)
+    }
+
+    override fun registerListener(listener: FaceAuthenticationListener) {
+        listeners.add(listener)
+    }
+
+    override fun unregisterListener(listener: FaceAuthenticationListener) {
+        listeners.remove(listener)
+    }
+
+    override fun isLockedOut(): Boolean = repository.isLockedOut.value
+
+    override fun isRunning(): Boolean = repository.isAuthRunning.value
+
+    override fun canFaceAuthRun(): Boolean = repository.canRunFaceAuth.value
+
+    override fun isEnabled(): Boolean {
+        return featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)
+    }
+
+    /** Provide the status of face authentication */
+    override val authenticationStatus = repository.authenticationStatus
+
+    /** Provide the status of face detection */
+    override val detectionStatus = repository.detectionStatus
+
+    private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) {
+        if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
+            applicationScope.launch {
+                faceAuthenticationLogger.authRequested(uiEvent)
+                repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
+            }
+        } else {
+            faceAuthenticationLogger.ignoredFaceAuthTrigger(
+                uiEvent,
+                ignoredReason = "Skipping face auth request because feature flag is false"
+            )
+        }
+    }
+
+    private fun observeFaceAuthStateUpdates() {
+        authenticationStatus
+            .onEach { authStatusUpdate ->
+                listeners.forEach { it.onAuthenticationStatusChanged(authStatusUpdate) }
+            }
+            .flowOn(mainDispatcher)
+            .launchIn(applicationScope)
+        detectionStatus
+            .onEach { detectionStatusUpdate ->
+                listeners.forEach { it.onDetectionStatusChanged(detectionStatusUpdate) }
+            }
+            .flowOn(mainDispatcher)
+            .launchIn(applicationScope)
+    }
+
+    companion object {
+        const val TAG = "KeyguardFaceAuthInteractor"
+    }
+}
+
+// Extension method that filters a generic Boolean flow to one that emits
+// whenever there is flip from false -> true
+private fun Flow<Boolean>.whenItFlipsToTrue(): Flow<Boolean> {
+    return this.pairwise()
+        .filter { pair -> !pair.previousValue && pair.newValue }
+        .map { it.newValue }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index f7355d5..7f6e4a9 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -4,6 +4,7 @@
 import android.hardware.face.FaceSensorPropertiesInternal
 import com.android.keyguard.FaceAuthUiEvent
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.log.dagger.FaceAuthLog
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel.DEBUG
@@ -27,15 +28,15 @@
 constructor(
     @FaceAuthLog private val logBuffer: LogBuffer,
 ) {
-    fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent) {
+    fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent, ignoredReason: String) {
         logBuffer.log(
             TAG,
             DEBUG,
-            { str1 = uiEvent.reason },
             {
-                "Ignoring trigger because face auth is currently running. " +
-                    "Trigger reason: $str1"
-            }
+                str1 = uiEvent.reason
+                str2 = ignoredReason
+            },
+            { "Ignoring trigger because $str2, Trigger reason: $str1" }
         )
     }
 
@@ -135,15 +136,6 @@
         logBuffer.log(TAG, DEBUG, "Face authentication failed")
     }
 
-    fun authenticationAcquired(acquireInfo: Int) {
-        logBuffer.log(
-            TAG,
-            DEBUG,
-            { int1 = acquireInfo },
-            { "Face acquired during face authentication: acquireInfo: $int1 " }
-        )
-    }
-
     fun authenticationError(
         errorCode: Int,
         errString: CharSequence?,
@@ -217,4 +209,34 @@
     fun cancellingFaceAuth() {
         logBuffer.log(TAG, DEBUG, "cancelling face auth because a gating condition became false")
     }
+
+    fun interactorStarted() {
+        logBuffer.log(TAG, DEBUG, "KeyguardFaceAuthInteractor started")
+    }
+
+    fun bouncerVisibilityChanged() {
+        logBuffer.log(TAG, DEBUG, "Triggering face auth because primary bouncer is visible")
+    }
+
+    fun alternateBouncerVisibilityChanged() {
+        logBuffer.log(TAG, DEBUG, "Triggering face auth because alternate bouncer is visible")
+    }
+
+    fun lockscreenBecameVisible(transitionStep: TransitionStep?) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            { str1 = "$transitionStep" },
+            { "Triggering face auth because lockscreen became visible due to transition: $str1" }
+        )
+    }
+
+    fun authRequested(uiEvent: FaceAuthUiEvent) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            { str1 = "$uiEvent" },
+            { "Requesting face auth for trigger: $str1" }
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index dbc2a5e..b29b588 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -19,7 +19,7 @@
 import android.content.Context
 import android.content.pm.PackageManager
 import android.graphics.drawable.Drawable
-import androidx.annotation.ColorRes
+import androidx.annotation.AttrRes
 import androidx.annotation.DrawableRes
 import com.android.systemui.R
 import com.android.systemui.common.shared.model.ContentDescription
@@ -108,7 +108,7 @@
 data class IconInfo(
     val contentDescription: ContentDescription,
     val icon: MediaTttIcon,
-    @ColorRes val tint: Int?,
+    @AttrRes val tint: Int?,
     /**
      * True if [drawable] is the app's icon, and false if [drawable] is some generic default icon.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 26b0e8d..b9ef916 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -55,6 +55,7 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.Surface;
+import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 import android.view.WindowInsets;
 import android.view.WindowManager;
@@ -173,7 +174,7 @@
                 }
             };
 
-
+    private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
     private final Context mContext;
     private final UserTracker mUserTracker;
     private final OverviewProxyService mOverviewProxyService;
@@ -901,6 +902,10 @@
                 Log.d(DEBUG_MISSING_GESTURE_TAG, "Start gesture: " + ev);
             }
 
+            // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new
+            // ACTION_DOWN, in that case we should just reuse the old instance.
+            mVelocityTracker.clear();
+
             // Verify if this is in within the touch region and we aren't in immersive mode, and
             // either the bouncer is showing or the notification panel is hidden
             mInputEventReceiver.setBatchingEnabled(false);
@@ -1027,11 +1032,30 @@
 
     private void dispatchToBackAnimation(MotionEvent event) {
         if (mBackAnimation != null) {
+            mVelocityTracker.addMovement(event);
+
+            final float velocityX;
+            final float velocityY;
+            if (event.getAction() == MotionEvent.ACTION_UP) {
+                // Compute the current velocity is expensive (see computeCurrentVelocity), so we
+                // are only doing it when the user completes the gesture.
+                int unitPixelPerSecond = 1000;
+                int maxVelocity = mViewConfiguration.getScaledMaximumFlingVelocity();
+                mVelocityTracker.computeCurrentVelocity(unitPixelPerSecond, maxVelocity);
+                velocityX = mVelocityTracker.getXVelocity();
+                velocityY = mVelocityTracker.getYVelocity();
+            } else {
+                velocityX = Float.NaN;
+                velocityY = Float.NaN;
+            }
+
             mBackAnimation.onBackMotion(
-                    event.getX(),
-                    event.getY(),
-                    event.getActionMasked(),
-                    mIsOnLeftEdge ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT);
+                    /* touchX = */ event.getX(),
+                    /* touchY = */ event.getY(),
+                    /* velocityX = */ velocityX,
+                    /* velocityY = */ velocityY,
+                    /* keyAction = */ event.getActionMasked(),
+                    /* swipeEdge = */ mIsOnLeftEdge ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 57b479e..856c64a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -20,8 +20,6 @@
 import android.os.Build;
 import android.provider.Settings;
 
-import com.android.internal.logging.InstanceId;
-import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.qs.QSTile;
@@ -55,11 +53,9 @@
         return tiles;
     }
 
-    void warn(String message, Throwable t);
     Context getContext();
     Context getUserContext();
     int getUserId();
-    UiEventLogger getUiEventLogger();
     Collection<QSTile> getTiles();
     void addCallback(Callback callback);
     void removeCallback(Callback callback);
@@ -107,8 +103,6 @@
 
     int indexOf(String tileSpec);
 
-    InstanceId getNewInstanceId();
-
     interface Callback {
         void onTilesChanged();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
index 14acb4b..67927a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
@@ -19,8 +19,6 @@
 import android.content.ComponentName
 import android.content.Context
 import androidx.annotation.GuardedBy
-import com.android.internal.logging.InstanceId
-import com.android.internal.logging.UiEventLogger
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dump.DumpManager
@@ -39,10 +37,9 @@
 
 /**
  * Adapter to determine what real class to use for classes that depend on [QSHost].
- *
  * * When [Flags.QS_PIPELINE_NEW_HOST] is off, all calls will be routed to [QSTileHost].
  * * When [Flags.QS_PIPELINE_NEW_HOST] is on, calls regarding the current set of tiles will be
- *   routed to [CurrentTilesInteractor]. Other calls (like [warn]) will still be routed to
+ *   routed to [CurrentTilesInteractor]. Other calls (like [createTileView]) will still be routed to
  *   [QSTileHost].
  *
  * This routing also includes dumps.
@@ -71,10 +68,7 @@
     init {
         scope.launch { tileServiceRequestControllerBuilder.create(this@QSHostAdapter).init() }
         // Redirect dump to the correct host (needed for CTS tests)
-        dumpManager.registerCriticalDumpable(
-            TAG,
-            if (useNewHost) interactor else qsTileHost
-        )
+        dumpManager.registerCriticalDumpable(TAG, if (useNewHost) interactor else qsTileHost)
     }
 
     override fun getTiles(): Collection<QSTile> {
@@ -103,10 +97,7 @@
 
     override fun addCallback(callback: QSHost.Callback) {
         if (useNewHost) {
-            val job =
-                scope.launch {
-                    interactor.currentTiles.collect { callback.onTilesChanged() }
-                }
+            val job = scope.launch { interactor.currentTiles.collect { callback.onTilesChanged() } }
             synchronized(callbacksMap) { callbacksMap.put(callback, job) }
         } else {
             qsTileHost.addCallback(callback)
@@ -147,10 +138,7 @@
 
     override fun addTile(component: ComponentName, end: Boolean) {
         if (useNewHost) {
-            interactor.addTile(
-                TileSpec.create(component),
-                if (end) POSITION_AT_END else 0
-            )
+            interactor.addTile(TileSpec.create(component), if (end) POSITION_AT_END else 0)
         } else {
             qsTileHost.addTile(component, end)
         }
@@ -164,10 +152,6 @@
         }
     }
 
-    override fun warn(message: String?, t: Throwable?) {
-        qsTileHost.warn(message, t)
-    }
-
     override fun getContext(): Context {
         return if (useNewHost) {
             context
@@ -192,10 +176,6 @@
         }
     }
 
-    override fun getUiEventLogger(): UiEventLogger {
-        return qsTileHost.uiEventLogger
-    }
-
     override fun createTileView(
         themedContext: Context?,
         tile: QSTile?,
@@ -219,8 +199,4 @@
     override fun indexOf(tileSpec: String): Int {
         return specs.indexOf(tileSpec)
     }
-
-    override fun getNewInstanceId(): InstanceId {
-        return qsTileHost.newInstanceId
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 0ca8973..59b94b7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -29,9 +29,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.InstanceId;
-import com.android.internal.logging.InstanceIdSequence;
-import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.Dumpable;
 import com.android.systemui.ProtoDumpable;
 import com.android.systemui.R;
@@ -91,7 +88,6 @@
         PanelInteractor, CustomTileAddedRepository {
     private static final String TAG = "QSTileHost";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    private static final int MAX_QS_INSTANCE_ID = 1 << 20;
 
     // Shared prefs that hold tile lifecycle info.
     @VisibleForTesting
@@ -103,8 +99,6 @@
     private final TunerService mTunerService;
     private final PluginManager mPluginManager;
     private final QSLogger mQSLogger;
-    private final UiEventLogger mUiEventLogger;
-    private final InstanceIdSequence mInstanceIdSequence;
     private final CustomTileStatePersister mCustomTileStatePersister;
     private final Executor mMainExecutor;
     private final UserFileManager mUserFileManager;
@@ -137,7 +131,6 @@
             Provider<AutoTileManager> autoTiles,
             Optional<CentralSurfaces> centralSurfacesOptional,
             QSLogger qsLogger,
-            UiEventLogger uiEventLogger,
             UserTracker userTracker,
             SecureSettings secureSettings,
             CustomTileStatePersister customTileStatePersister,
@@ -150,13 +143,11 @@
         mTunerService = tunerService;
         mPluginManager = pluginManager;
         mQSLogger = qsLogger;
-        mUiEventLogger = uiEventLogger;
         mMainExecutor = mainExecutor;
         mTileLifeCycleManagerFactory = tileLifecycleManagerFactory;
         mUserFileManager = userFileManager;
         mFeatureFlags = featureFlags;
 
-        mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID);
         mCentralSurfacesOptional = centralSurfacesOptional;
 
         mQsFactories.add(defaultFactory);
@@ -175,11 +166,6 @@
         });
     }
 
-    @Override
-    public InstanceId getNewInstanceId() {
-        return mInstanceIdSequence.newInstanceId();
-    }
-
     public void destroy() {
         mTiles.values().forEach(tile -> tile.destroy());
         mAutoTiles.destroy();
@@ -207,11 +193,6 @@
     }
 
     @Override
-    public UiEventLogger getUiEventLogger() {
-        return mUiEventLogger;
-    }
-
-    @Override
     public void addCallback(Callback callback) {
         mCallbacks.add(callback);
     }
@@ -227,11 +208,6 @@
     }
 
     @Override
-    public void warn(String message, Throwable t) {
-        // already logged
-    }
-
-    @Override
     public void collapsePanels() {
         mCentralSurfacesOptional.ifPresent(CentralSurfaces::postAnimateCollapsePanels);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QsEventLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QsEventLogger.kt
new file mode 100644
index 0000000..fc739ed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QsEventLogger.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.InstanceIdSequence
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+interface QsEventLogger : UiEventLogger {
+    fun getNewInstanceId(): InstanceId
+}
+
+@SysUISingleton
+class QsEventLoggerImpl
+@Inject
+constructor(
+    uiEventLogger: UiEventLogger,
+) : QsEventLogger, UiEventLogger by uiEventLogger {
+
+    companion object {
+        private const val MAX_QS_INSTANCE_ID = 1 shl 20
+    }
+
+    val sequence = InstanceIdSequence(MAX_QS_INSTANCE_ID)
+    override fun getNewInstanceId(): InstanceId {
+        return sequence.newInstanceId()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
index 3ddd9f1c..1f63f5d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
@@ -21,6 +21,8 @@
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QSHostAdapter
 import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.QsEventLoggerImpl
 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedSharedPrefsRepository
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
@@ -34,8 +36,12 @@
 
     @Binds fun provideQsHost(controllerImpl: QSHostAdapter): QSHost
 
+    @Binds fun provideEventLogger(impl: QsEventLoggerImpl): QsEventLogger
+
     @Module
     companion object {
+        private const val MAX_QS_INSTANCE_ID = 1 shl 20
+
         @Provides
         @JvmStatic
         fun providePanelInteractor(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index d4854e1..897b0e7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -59,17 +59,20 @@
 import com.android.systemui.plugins.qs.QSTile.State;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.DisplayTracker;
 
+import dagger.Lazy;
+
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
+
 
 public class CustomTile extends QSTileImpl<State> implements TileChangeListener {
     public static final String PREFIX = "custom(";
@@ -111,6 +114,7 @@
 
     private CustomTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             Looper backgroundLooper,
             Handler mainHandler,
             FalsingManager falsingManager,
@@ -124,7 +128,7 @@
             TileServices tileServices,
             DisplayTracker displayTracker
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mTileServices = tileServices;
         mWindowManager = WindowManagerGlobal.getWindowManagerService();
@@ -561,6 +565,7 @@
 
     public static class Builder {
         final Lazy<QSHost> mQSHostLazy;
+        final QsEventLogger mUiEventLogger;
         final Looper mBackgroundLooper;
         final Handler mMainHandler;
         private final FalsingManager mFalsingManager;
@@ -578,6 +583,7 @@
         @Inject
         public Builder(
                 Lazy<QSHost> hostLazy,
+                QsEventLogger uiEventLogger,
                 @Background Looper backgroundLooper,
                 @Main Handler mainHandler,
                 FalsingManager falsingManager,
@@ -590,6 +596,7 @@
                 DisplayTracker displayTracker
         ) {
             mQSHostLazy = hostLazy;
+            mUiEventLogger = uiEventLogger;
             mBackgroundLooper = backgroundLooper;
             mMainHandler = mainHandler;
             mFalsingManager = falsingManager;
@@ -620,6 +627,7 @@
             String action = getAction(mSpec);
             return new CustomTile(
                     mQSHostLazy.get(),
+                    mUiEventLogger,
                     mBackgroundLooper,
                     mMainHandler,
                     mFalsingManager,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 49ba508..2a9e7d0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -66,6 +66,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSEvent;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SideLabelTileLayout;
 import com.android.systemui.qs.logging.QSLogger;
 
@@ -179,6 +180,7 @@
 
     protected QSTileImpl(
             QSHost host,
+            QsEventLogger uiEventLogger,
             Looper backgroundLooper,
             Handler mainHandler,
             FalsingManager falsingManager,
@@ -189,8 +191,8 @@
     ) {
         mHost = host;
         mContext = host.getContext();
-        mInstanceId = host.getNewInstanceId();
-        mUiEventLogger = host.getUiEventLogger();
+        mInstanceId = uiEventLogger.getNewInstanceId();
+        mUiEventLogger = uiEventLogger;
 
         mUiHandler = mainHandler;
         mHandler = new H(backgroundLooper);
@@ -633,7 +635,6 @@
             } catch (Throwable t) {
                 final String error = "Error in " + name;
                 Log.w(TAG, error, t);
-                mHost.warn(error, t);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 92a83bb..30765f7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -45,15 +45,18 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.GlobalSettings;
 
+import dagger.Lazy;
+
 import javax.inject.Inject;
 
-import dagger.Lazy;
+
 
 /** Quick settings tile: Airplane mode **/
 public class AirplaneModeTile extends QSTileImpl<BooleanState> {
@@ -69,6 +72,7 @@
     @Inject
     public AirplaneModeTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -81,7 +85,7 @@
             GlobalSettings globalSettings,
             UserTracker userTracker
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mBroadcastDispatcher = broadcastDispatcher;
         mLazyConnectivityManager = lazyConnectivityManager;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
index 2ca452e..c709969 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.settings.UserTracker
@@ -31,6 +32,7 @@
 
 class AlarmTile @Inject constructor(
     host: QSHost,
+    uiEventLogger: QsEventLogger,
     @Background backgroundLooper: Looper,
     @Main mainHandler: Handler,
     falsingManager: FalsingManager,
@@ -42,6 +44,7 @@
     nextAlarmController: NextAlarmController
 ) : QSTileImpl<QSTile.State>(
     host,
+    uiEventLogger,
     backgroundLooper,
     mainHandler,
     falsingManager,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index 027a464..a444e76 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -62,6 +63,7 @@
     @Inject
     public BatterySaverTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -72,7 +74,7 @@
             BatteryController batteryController,
             SecureSettings secureSettings
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mBatteryController = batteryController;
         mBatteryController.observe(getLifecycle(), this);
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 08fe270..30218a6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -47,6 +47,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.BluetoothController;
@@ -74,6 +75,7 @@
     @Inject
     public BluetoothTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -83,7 +85,7 @@
             QSLogger qsLogger,
             BluetoothController bluetoothController
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = bluetoothController;
         mController.observe(getLifecycle(), mCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
index 93e5f1e..65ef6b9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -48,7 +49,9 @@
     public static final String TILE_SPEC = "cameratoggle";
 
     @Inject
-    protected CameraToggleTile(QSHost host,
+    protected CameraToggleTile(
+            QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             MetricsLogger metricsLogger,
@@ -58,7 +61,7 @@
             QSLogger qsLogger,
             IndividualSensorPrivacyController sensorPrivacyController,
             KeyguardStateController keyguardStateController) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger, sensorPrivacyController,
                 keyguardStateController);
     }
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 8d98481..54376fe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -47,6 +47,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.connectivity.NetworkController;
@@ -84,6 +85,7 @@
     @Inject
     public CastTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -97,7 +99,7 @@
             HotspotController hotspotController,
             DialogLaunchAnimator dialogLaunchAnimator
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = castController;
         mKeyguard = keyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
index b6205d5..cf9e346 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -56,6 +57,7 @@
     @Inject
     public ColorCorrectionTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -66,7 +68,7 @@
             UserTracker userTracker,
             SecureSettings secureSettings
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
 
         mSetting = new SettingObserver(secureSettings, mHandler,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 9a44e83..4ecde61 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -38,6 +38,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -55,6 +56,7 @@
     @Inject
     public ColorInversionTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -65,7 +67,7 @@
             UserTracker userTracker,
             SecureSettings secureSettings
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
 
         mSetting = new SettingObserver(secureSettings, mHandler,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index add517e..e769b5e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -38,6 +38,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -58,6 +59,7 @@
     @Inject
     public DataSaverTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -68,7 +70,7 @@
             DataSaverController dataSaverController,
             DialogLaunchAnimator dialogLaunchAnimator
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mDataSaverController = dataSaverController;
         mDialogLaunchAnimator = dialogLaunchAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
index 01164fb..ddaff3b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -40,6 +40,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import java.util.concurrent.atomic.AtomicBoolean
@@ -47,6 +48,7 @@
 
 class DeviceControlsTile @Inject constructor(
     host: QSHost,
+    uiEventLogger: QsEventLogger,
     @Background backgroundLooper: Looper,
     @Main mainHandler: Handler,
     falsingManager: FalsingManager,
@@ -56,14 +58,15 @@
     qsLogger: QSLogger,
     private val controlsComponent: ControlsComponent
 ) : QSTileImpl<QSTile.State>(
-        host,
-        backgroundLooper,
-        mainHandler,
-        falsingManager,
-        metricsLogger,
-        statusBarStateController,
-        activityStarter,
-        qsLogger
+    host,
+    uiEventLogger,
+    backgroundLooper,
+    mainHandler,
+    falsingManager,
+    metricsLogger,
+    statusBarStateController,
+    activityStarter,
+    qsLogger
 ) {
 
     private var hasControlsApps = AtomicBoolean(false)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 434fe45..3e7bdd1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -54,6 +54,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -88,6 +89,7 @@
     @Inject
     public DndTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -100,7 +102,7 @@
             SecureSettings secureSettings,
             DialogLaunchAnimator dialogLaunchAnimator
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = zenModeController;
         mSharedPreferences = sharedPreferences;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
index f913326..eef4c1d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -48,6 +48,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -90,6 +91,7 @@
     @Inject
     public DreamTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -105,7 +107,7 @@
             @Named(DreamModule.DREAM_ONLY_ENABLED_FOR_DOCK_USER)
                     boolean dreamOnlyEnabledForDockUser
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mDreamManager = dreamManager;
         mBroadcastDispatcher = broadcastDispatcher;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index e091a75..2c986da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.FlashlightController;
@@ -55,6 +56,7 @@
     @Inject
     public FlashlightTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -64,7 +66,7 @@
             QSLogger qsLogger,
             FlashlightController flashlightController
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mFlashlightController = flashlightController;
         mFlashlightController.observe(getLifecycle(), this);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
index 3f514344..12d9847 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -47,6 +48,7 @@
 @Inject
 constructor(
     host: QSHost,
+    uiEventLogger: QsEventLogger,
     @Background backgroundLooper: Looper,
     @Main mainHandler: Handler,
     falsingManager: FalsingManager,
@@ -61,6 +63,7 @@
 ) :
     QSTileImpl<QSTile.State?>(
         host,
+        uiEventLogger,
         backgroundLooper,
         mainHandler,
         falsingManager,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 6bf8b76..4c3699c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -41,6 +41,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.DataSaverController;
@@ -61,6 +62,7 @@
     @Inject
     public HotspotTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -71,7 +73,7 @@
             HotspotController hotspotController,
             DataSaverController dataSaverController
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mHotspotController = hotspotController;
         mDataSaverController = dataSaverController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 75d0172..f16f0dc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -51,6 +51,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.AlphaControlledSignalTileView;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
@@ -90,6 +91,7 @@
     @Inject
     public InternetTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -101,7 +103,7 @@
             AccessPointController accessPointController,
             InternetDialogFactory internetDialogFactory
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mInternetDialogFactory = internetDialogFactory;
         mHandler = mainHandler;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index 27f5826..83c5688 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -59,6 +60,7 @@
     @Inject
     public LocationTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -70,7 +72,7 @@
             KeyguardStateController keyguardStateController,
             PanelInteractor panelInteractor
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = locationController;
         mKeyguard = keyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
index 2e475d4..86a6a8c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -48,7 +49,9 @@
     public static final String TILE_SPEC = "mictoggle";
 
     @Inject
-    protected MicrophoneToggleTile(QSHost host,
+    protected MicrophoneToggleTile(
+            QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             MetricsLogger metricsLogger,
@@ -58,7 +61,7 @@
             QSLogger qsLogger,
             IndividualSensorPrivacyController sensorPrivacyController,
             KeyguardStateController keyguardStateController) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger, sensorPrivacyController,
                 keyguardStateController);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
index e189f80..29ccb76 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
@@ -43,6 +43,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 
@@ -65,6 +66,7 @@
     @Inject
     public NfcTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -74,7 +76,7 @@
             QSLogger qsLogger,
             BroadcastDispatcher broadcastDispatcher
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mBroadcastDispatcher = broadcastDispatcher;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index aacd53b..405e139 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -45,6 +45,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.LocationController;
@@ -80,6 +81,7 @@
     @Inject
     public NightDisplayTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -91,7 +93,7 @@
             ColorDisplayManager colorDisplayManager,
             NightDisplayListenerModule.Builder nightDisplayListenerBuilder
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mLocationController = locationController;
         mManager = colorDisplayManager;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
index ae67d99..1eb317a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
@@ -36,6 +36,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -57,6 +58,7 @@
     @Inject
     public OneHandedModeTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -66,7 +68,7 @@
             QSLogger qsLogger,
             UserTracker userTracker,
             SecureSettings secureSettings) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mSetting = new SettingObserver(secureSettings, mHandler,
                 Settings.Secure.ONE_HANDED_MODE_ENABLED, userTracker.getUserId()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
index 92f5272..9e365d3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 
@@ -62,6 +63,7 @@
     @Inject
     public QRCodeScannerTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -70,7 +72,7 @@
             ActivityStarter activityStarter,
             QSLogger qsLogger,
             QRCodeScannerController qrCodeScannerController) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mQRCodeScannerController = qrCodeScannerController;
         mQRCodeScannerController.observe(getLifecycle(), mCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index 4a3c563..e026bdb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -49,6 +49,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -83,6 +84,7 @@
     @Inject
     public QuickAccessWalletTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -94,7 +96,7 @@
             PackageManager packageManager,
             SecureSettings secureSettings,
             QuickAccessWalletController quickAccessWalletController) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = quickAccessWalletController;
         mKeyguardStateController = keyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
index 10f1ce4..2e04afb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
@@ -38,6 +38,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.ReduceBrightColorsController;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -59,6 +60,7 @@
             @Named(RBC_AVAILABLE) boolean isAvailable,
             ReduceBrightColorsController reduceBrightColorsController,
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -67,7 +69,7 @@
             ActivityStarter activityStarter,
             QSLogger qsLogger
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mReduceBrightColorsController = reduceBrightColorsController;
         mReduceBrightColorsController.observe(getLifecycle(), this);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 8888c73..7f7f8ad6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -44,6 +44,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -71,6 +72,7 @@
     @Inject
     public RotationLockTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -83,7 +85,7 @@
             BatteryController batteryController,
             SecureSettings secureSettings
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = rotationLockController;
         mController.observe(this, mCallback);
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 65592a7..2d4652d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -41,6 +41,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -74,6 +75,7 @@
     @Inject
     public ScreenRecordTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -88,7 +90,7 @@
             DialogLaunchAnimator dialogLaunchAnimator,
             PanelInteractor panelInteractor
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = controller;
         mController.observe(this, mCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index d99c1d1..7c4f097 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -39,6 +39,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
@@ -68,7 +69,9 @@
      */
     public abstract String getRestriction();
 
-    protected SensorPrivacyToggleTile(QSHost host,
+    protected SensorPrivacyToggleTile(
+            QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -78,7 +81,7 @@
             QSLogger qsLogger,
             IndividualSensorPrivacyController sensorPrivacyController,
             KeyguardStateController keyguardStateController) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mSensorPrivacyController = sensorPrivacyController;
         mKeyguard = keyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index 809689c..a60d1ad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -39,6 +39,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -69,6 +70,7 @@
     @Inject
     public UiModeNightTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -80,7 +82,7 @@
             BatteryController batteryController,
             LocationController locationController
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mBatteryController = batteryController;
         mUiModeManager = host.getUserContext().getSystemService(UiModeManager.class);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 6a5c990..17e72e5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -40,6 +40,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
@@ -59,6 +60,7 @@
     @Inject
     public WorkModeTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -68,7 +70,7 @@
             QSLogger qsLogger,
             ManagedProfileController managedProfileController
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mProfileController = managedProfileController;
         mProfileController.observe(getLifecycle(), this);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index aedd976..222a0f4f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -3333,7 +3333,10 @@
 
         mGestureRecorder = recorder;
         mHideExpandedRunnable = hideExpandedRunnable;
-        mNotificationStackScrollLayoutController.setShelfController(notificationShelfController);
+        if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+            mNotificationStackScrollLayoutController.setShelfController(
+                    notificationShelfController);
+        }
         mNotificationShelfController = notificationShelfController;
         mLockscreenShadeTransitionController.bindController(notificationShelfController);
         updateMaxDisplayedNotifications(true);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 7cb1cbe..ef14d1c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -64,6 +64,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.plugins.FalsingManager;
@@ -132,6 +133,7 @@
     private final FalsingCollector mFalsingCollector;
     private final LockscreenGestureLogger mLockscreenGestureLogger;
     private final ShadeLogger mShadeLog;
+    private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
     private final FeatureFlags mFeatureFlags;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final FalsingManager mFalsingManager;
@@ -318,7 +320,8 @@
             MetricsLogger metricsLogger,
             FeatureFlags featureFlags,
             InteractionJankMonitor interactionJankMonitor,
-            ShadeLogger shadeLog
+            ShadeLogger shadeLog,
+            KeyguardFaceAuthInteractor keyguardFaceAuthInteractor
     ) {
         mPanelViewControllerLazy = panelViewControllerLazy;
         mPanelView = panelView;
@@ -357,6 +360,7 @@
         mLockscreenGestureLogger = lockscreenGestureLogger;
         mMetricsLogger = metricsLogger;
         mShadeLog = shadeLog;
+        mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
         mFeatureFlags = featureFlags;
         mInteractionJankMonitor = interactionJankMonitor;
 
@@ -937,6 +941,7 @@
         // When expanding QS, let's authenticate the user if possible,
         // this will speed up notification actions.
         if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) {
+            mKeyguardFaceAuthInteractor.onQsExpansionStared();
             mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.QS_EXPANDED);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index d1c6aef..7eb63da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -53,6 +53,8 @@
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
+import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
 import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -99,6 +101,9 @@
     private boolean mSensitiveRevealAnimEndabled;
     private boolean mShelfRefactorFlagEnabled;
     private boolean mCanModifyColorOfNotifications;
+    private boolean mCanInteract;
+    private NotificationStackScrollLayout mHostLayout;
+    private NotificationRoundnessManager mRoundnessManager;
 
     public NotificationShelf(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -135,6 +140,7 @@
 
     public void bind(AmbientState ambientState,
                      NotificationStackScrollLayoutController hostLayoutController) {
+        assertRefactorFlagDisabled();
         mAmbientState = ambientState;
         mHostLayoutController = hostLayoutController;
         hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> {
@@ -142,6 +148,14 @@
         });
     }
 
+    public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout,
+            NotificationRoundnessManager roundnessManager) {
+        if (!checkRefactorFlagEnabled()) return;
+        mAmbientState = ambientState;
+        mHostLayout = hostLayout;
+        mRoundnessManager = roundnessManager;
+    }
+
     private void updateResources() {
         Resources res = getResources();
         mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
@@ -233,7 +247,7 @@
             } else {
                 viewState.setAlpha(1f - ambientState.getHideAmount());
             }
-            viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0;
+            viewState.belowSpeedBump = getSpeedBumpIndex() == 0;
             viewState.hideSensitive = false;
             viewState.setXTranslation(getTranslationX());
             viewState.hasItemsInStableShelf = lastViewState.inShelf;
@@ -276,6 +290,14 @@
         }
     }
 
+    private int getSpeedBumpIndex() {
+        if (mShelfRefactorFlagEnabled) {
+            return mHostLayout.getSpeedBumpIndex();
+        } else {
+            return mHostLayoutController.getSpeedBumpIndex();
+        }
+    }
+
     /**
      * @param fractionToShade Fraction of lockscreen to shade transition
      * @param shortestWidth   Shortest width to use for lockscreen shelf
@@ -388,8 +410,8 @@
         int baseZHeight = mAmbientState.getBaseZHeight();
         int clipTopAmount = 0;
 
-        for (int i = 0; i < mHostLayoutController.getChildCount(); i++) {
-            ExpandableView child = mHostLayoutController.getChildAt(i);
+        for (int i = 0; i < getHostLayoutChildCount(); i++) {
+            ExpandableView child = getHostLayoutChildAt(i);
             if (!child.needsClippingToShelf() || child.getVisibility() == GONE) {
                 continue;
             }
@@ -474,11 +496,11 @@
 
         // TODO(b/172289889) transition last icon in shelf to notification icon and vice versa.
         setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
-        mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex());
+        mShelfIcons.setSpeedBumpIndex(getSpeedBumpIndex());
         mShelfIcons.calculateIconXTranslations();
         mShelfIcons.applyIconStates();
-        for (int i = 0; i < mHostLayoutController.getChildCount(); i++) {
-            View child = mHostLayoutController.getChildAt(i);
+        for (int i = 0; i < getHostLayoutChildCount(); i++) {
+            View child = getHostLayoutChildAt(i);
             if (!(child instanceof ExpandableNotificationRow)
                     || child.getVisibility() == GONE) {
                 continue;
@@ -493,6 +515,22 @@
         }
     }
 
+    private ExpandableView getHostLayoutChildAt(int index) {
+        if (mShelfRefactorFlagEnabled) {
+            return (ExpandableView) mHostLayout.getChildAt(index);
+        } else {
+            return mHostLayoutController.getChildAt(index);
+        }
+    }
+
+    private int getHostLayoutChildCount() {
+        if (mShelfRefactorFlagEnabled) {
+            return mHostLayout.getChildCount();
+        } else {
+            return mHostLayoutController.getChildCount();
+        }
+    }
+
     private boolean canModifyColorOfNotifications() {
         if (mShelfRefactorFlagEnabled) {
             return mCanModifyColorOfNotifications && mAmbientState.isShadeExpanded();
@@ -515,7 +553,7 @@
                 && anv == mAmbientState.getTrackedHeadsUpRow();
 
         final boolean shouldUpdateCornerRoundness = viewStart < shelfStart
-                && !mHostLayoutController.isViewAffectedBySwipe(anv)
+                && !isViewAffectedBySwipe(anv)
                 && !isUnlockedHeadsUp
                 && !isHunGoingToShade
                 && !anv.isAboveShelf()
@@ -567,6 +605,14 @@
         anv.requestBottomRoundness(bottomValue, sourceType, /* animate = */ false);
     }
 
+    private boolean isViewAffectedBySwipe(ExpandableView expandableView) {
+        if (!mShelfRefactorFlagEnabled) {
+            return mHostLayoutController.isViewAffectedBySwipe(expandableView);
+        } else {
+            return mRoundnessManager.isViewAffectedBySwipe(expandableView);
+        }
+    }
+
     /**
      * Clips transient views to the top of the shelf - Transient views are only used for
      * disappearing views/animations and need to be clipped correctly by the shelf to ensure they
@@ -574,8 +620,8 @@
      * swipes quickly.
      */
     private void clipTransientViews() {
-        for (int i = 0; i < mHostLayoutController.getTransientViewCount(); i++) {
-            View transientView = mHostLayoutController.getTransientView(i);
+        for (int i = 0; i < getHostLayoutTransientViewCount(); i++) {
+            View transientView = getHostLayoutTransientView(i);
             if (transientView instanceof ExpandableView) {
                 ExpandableView transientExpandableView = (ExpandableView) transientView;
                 updateNotificationClipHeight(transientExpandableView, getTranslationY(), -1);
@@ -583,6 +629,22 @@
         }
     }
 
+    private View getHostLayoutTransientView(int index) {
+        if (mShelfRefactorFlagEnabled) {
+            return mHostLayout.getTransientView(index);
+        } else {
+            return mHostLayoutController.getTransientView(index);
+        }
+    }
+
+    private int getHostLayoutTransientViewCount() {
+        if (mShelfRefactorFlagEnabled) {
+            return mHostLayout.getTransientViewCount();
+        } else {
+            return mHostLayoutController.getTransientViewCount();
+        }
+    }
+
     private void updateIconClipAmount(ExpandableNotificationRow row) {
         float maxTop = row.getTranslationY();
         if (getClipTopAmount() != 0) {
@@ -922,18 +984,27 @@
 
     @Override
     public void onStateChanged(int newState) {
+        assertRefactorFlagDisabled();
         mStatusBarState = newState;
         updateInteractiveness();
     }
 
     private void updateInteractiveness() {
-        mInteractive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf;
+        mInteractive = canInteract() && mHasItemsInStableShelf;
         setClickable(mInteractive);
         setFocusable(mInteractive);
         setImportantForAccessibility(mInteractive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
                 : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
     }
 
+    private boolean canInteract() {
+        if (mShelfRefactorFlagEnabled) {
+            return mCanInteract;
+        } else {
+            return mStatusBarState == StatusBarState.KEYGUARD;
+        }
+    }
+
     @Override
     protected boolean isInteractive() {
         return mInteractive;
@@ -972,8 +1043,7 @@
 
     private void assertRefactorFlagDisabled() {
         if (mShelfRefactorFlagEnabled) {
-            throw new IllegalStateException(
-                    "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is enabled.");
+            NotificationShelfController.throwIllegalFlagStateError(false);
         }
     }
 
@@ -995,8 +1065,22 @@
         mCanModifyColorOfNotifications = canModifyColorOfNotifications;
     }
 
+    public void setCanInteract(boolean canInteract) {
+        if (!checkRefactorFlagEnabled()) return;
+        mCanInteract = canInteract;
+        updateInteractiveness();
+    }
+
     public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) {
-        mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
+        mIndexOfFirstViewInShelf = getIndexOfViewInHostLayout(firstViewInShelf);
+    }
+
+    private int getIndexOfViewInHostLayout(ExpandableView child) {
+        if (mShelfRefactorFlagEnabled) {
+            return mHostLayout.indexOfChild(child);
+        } else {
+            return mHostLayoutController.indexOfChild(child);
+        }
     }
 
     /**
@@ -1011,6 +1095,11 @@
         mShelfRefactorFlagEnabled = enabled;
     }
 
+    public void requestRoundnessResetFor(ExpandableView child) {
+        if (!checkRefactorFlagEnabled()) return;
+        child.requestRoundnessReset(SHELF_SCROLL);
+    }
+
     /**
      * This method resets the OnScroll roundness of a view to 0f
      * <p>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
index bf3d47c..07cfd0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
@@ -16,8 +16,11 @@
 
 package com.android.systemui.statusbar
 
+import android.util.Log
 import android.view.View
 import android.view.View.OnClickListener
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView.OnActivatedListener
 import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -51,4 +54,29 @@
 
     /** @see View.setOnClickListener */
     fun setOnClickListener(listener: OnClickListener)
+
+    companion object {
+        @JvmStatic
+        fun assertRefactorFlagDisabled(featureFlags: FeatureFlags) {
+            if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+                throwIllegalFlagStateError(expected = false)
+            }
+        }
+
+        @JvmStatic
+        fun checkRefactorFlagEnabled(featureFlags: FeatureFlags): Boolean =
+            featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR).also { enabled ->
+                if (!enabled) {
+                    Log.wtf("NotifShelf", getErrorMessage(expected = true))
+                }
+            }
+
+        @JvmStatic
+        fun throwIllegalFlagStateError(expected: Boolean): Nothing =
+            error(getErrorMessage(expected))
+
+        private fun getErrorMessage(expected: Boolean): String =
+            "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is " +
+                if (expected) "disabled" else "enabled"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
index ecd0c41..fcff437 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
@@ -48,6 +48,10 @@
             View.SCALE_Y, R.id.scale_y_animator_tag, R.id.scale_y_animator_start_value_tag,
             R.id.scale_y_animator_end_value_tag);
 
+    public static final AnimatableProperty ALPHA = AnimatableProperty.from(
+            View.ALPHA, R.id.alpha_animator_tag, R.id.alpha_animator_start_value_tag,
+            R.id.alpha_animator_end_value_tag);
+
     /**
      * Similar to X, however this doesn't allow for any other modifications other than from this
      * property. When using X, it's possible that the view is laid out during the animation,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index a9d1255..950ab5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -299,6 +299,16 @@
      */
     private boolean mShowGroupBackgroundWhenExpanded;
 
+    /**
+     * True if we always show the collapsed layout on lockscreen because vertical space is low.
+     */
+    private boolean mSaveSpaceOnLockscreen;
+
+    /**
+     * True if we use intrinsic height regardless of vertical space available on lockscreen.
+     */
+    private boolean mIgnoreLockscreenConstraints;
+
     private OnClickListener mExpandClickListener = new OnClickListener() {
         @Override
         public void onClick(View v) {
@@ -394,6 +404,14 @@
         return mGroupExpansionChanging;
     }
 
+    public void setSaveSpaceOnLockscreen(boolean saveSpace) {
+        mSaveSpaceOnLockscreen = saveSpace;
+    }
+
+    public boolean getSaveSpaceOnLockscreen() {
+        return mSaveSpaceOnLockscreen;
+    }
+
     public void setGroupExpansionChanging(boolean changing) {
         mGroupExpansionChanging = changing;
     }
@@ -419,15 +437,9 @@
      */
     public void setAnimationRunning(boolean running) {
         // Sets animations running in the private/public layouts.
-        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE)) {
-            for (NotificationContentView l : mLayouts) {
-                if (l != null) {
-                    l.setContentAnimationRunning(running);
-                    setIconAnimationRunning(running, l);
-                }
-            }
-        } else {
-            for (NotificationContentView l : mLayouts) {
+        for (NotificationContentView l : mLayouts) {
+            if (l != null) {
+                l.setContentAnimationRunning(running);
                 setIconAnimationRunning(running, l);
             }
         }
@@ -2553,11 +2565,18 @@
     }
 
     @Override
+    public int getHeightWithoutLockscreenConstraints() {
+        mIgnoreLockscreenConstraints = true;
+        final int height = getIntrinsicHeight();
+        mIgnoreLockscreenConstraints = false;
+        return height;
+    }
+
+    @Override
     public int getIntrinsicHeight() {
         if (isUserLocked()) {
             return getActualHeight();
-        }
-        if (mGuts != null && mGuts.isExposed()) {
+        } else if (mGuts != null && mGuts.isExposed()) {
             return mGuts.getIntrinsicHeight();
         } else if ((isChildInGroup() && !isGroupExpanded())) {
             return mPrivateLayout.getMinHeight();
@@ -2579,13 +2598,14 @@
             return getCollapsedHeight();
         }
     }
-
     /**
      * @return {@code true} if the notification can show it's heads up layout. This is mostly true
      * except for legacy use cases.
      */
     public boolean canShowHeadsUp() {
-        if (mOnKeyguard && !isDozing() && !isBypassEnabled() && !mEntry.isStickyAndNotDemoted()) {
+        if (mOnKeyguard && !isDozing() && !isBypassEnabled() &&
+                (!mEntry.isStickyAndNotDemoted()
+                        || (!mIgnoreLockscreenConstraints && mSaveSpaceOnLockscreen))) {
             return false;
         }
         return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 9df6ba9..5edff5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -291,6 +291,11 @@
             long duration) {
     }
 
+    public int getHeightWithoutLockscreenConstraints() {
+        // ExpandableNotificationRow overrides this.
+        return getHeight();
+    }
+
     /**
      * @return The desired notification height.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
index db550c0..8ba65f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
@@ -32,6 +32,10 @@
     private val keyguardRepository: KeyguardRepository,
     private val deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
 ) {
+    /** Is the shelf showing on the keyguard? */
+    val isShowingOnKeyguard: Flow<Boolean>
+        get() = keyguardRepository.isKeyguardShowing
+
     /** Is the system in a state where the shelf is just a static display of notification icons? */
     val isShelfStatic: Flow<Boolean>
         get() =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
index bd531ca..b190cf6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.notification.shelf.ui.viewbinder
 
 import android.view.View
-import android.view.View.OnAttachStateChangeListener
 import android.view.accessibility.AccessibilityManager
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
@@ -29,7 +28,6 @@
 import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.NotificationShelfController
-import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController
 import com.android.systemui.statusbar.notification.row.ExpandableOutlineViewController
@@ -40,9 +38,11 @@
 import com.android.systemui.statusbar.phone.NotificationIconContainer
 import com.android.systemui.statusbar.phone.NotificationTapHelper
 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import com.android.systemui.util.kotlin.getValue
+import dagger.Lazy
+import javax.inject.Inject
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import javax.inject.Inject
 
 /**
  * Controller class for [NotificationShelf]. This implementation serves as a temporary wrapper
@@ -61,11 +61,13 @@
     private val a11yManager: AccessibilityManager,
     private val falsingManager: FalsingManager,
     private val falsingCollector: FalsingCollector,
-    private val statusBarStateController: SysuiStatusBarStateController,
+    hostControllerLazy: Lazy<NotificationStackScrollLayoutController>,
 ) : NotificationShelfController {
 
+    private val hostController: NotificationStackScrollLayoutController by hostControllerLazy
+
     override val view: NotificationShelf
-        get() = shelf
+        get() = unsupported
 
     init {
         shelf.apply {
@@ -87,22 +89,9 @@
                 falsingCollector,
             )
             .init()
-        val onAttachStateListener =
-            object : OnAttachStateChangeListener {
-                override fun onViewAttachedToWindow(v: View) {
-                    statusBarStateController.addCallback(
-                        shelf,
-                        SysuiStatusBarStateController.RANK_SHELF,
-                    )
-                }
-
-                override fun onViewDetachedFromWindow(v: View) {
-                    statusBarStateController.removeCallback(shelf)
-                }
-            }
-        shelf.addOnAttachStateChangeListener(onAttachStateListener)
-        if (shelf.isAttachedToWindow) {
-            onAttachStateListener.onViewAttachedToWindow(shelf)
+        hostController.setShelf(shelf)
+        hostController.setOnNotificationRemovedListener { child, _ ->
+            view.requestRoundnessResetFor(child)
         }
     }
 
@@ -121,16 +110,14 @@
     override fun bind(
         ambientState: AmbientState,
         notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
-    ) {
-        shelf.bind(ambientState, notificationStackScrollLayoutController)
-    }
+    ) = unsupported
 
     override fun setOnClickListener(listener: View.OnClickListener) {
         shelf.setOnClickListener(listener)
     }
 
     private val unsupported: Nothing
-        get() = error("Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is enabled")
+        get() = NotificationShelfController.throwIllegalFlagStateError(expected = true)
 }
 
 /** Binds a [NotificationShelf] to its backend. */
@@ -141,6 +128,7 @@
                 viewModel.canModifyColorOfNotifications
                     .onEach(shelf::setCanModifyColorOfNotifications)
                     .launchIn(this)
+                viewModel.isClickable.onEach(shelf::setCanInteract).launchIn(this)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
index b84834a..5e297c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
@@ -30,6 +30,10 @@
 constructor(
     private val interactor: NotificationShelfInteractor,
 ) {
+    /** Is the shelf allowed to be clickable when it has content? */
+    val isClickable: Flow<Boolean>
+        get() = interactor.isShowingOnKeyguard
+
     /** Is the shelf allowed to modify the color of notifications in the host layout? */
     val canModifyColorOfNotifications: Flow<Boolean>
         get() = interactor.isShelfStatic.map { static -> !static }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java
index 112d48c..00b9aa4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java
@@ -32,6 +32,7 @@
     public long duration;
     public long delay;
     private ArrayMap<Property, Interpolator> mInterpolatorMap;
+    private Consumer<Property> mAnimationCancelAction;
     private Consumer<Property> mAnimationEndAction;
 
     /**
@@ -50,27 +51,43 @@
      * @return a listener that will be added for a given property during its animation.
      */
     public AnimatorListenerAdapter getAnimationFinishListener(Property property) {
-        if (mAnimationEndAction == null) {
+        if (mAnimationEndAction == null && mAnimationCancelAction == null) {
             return null;
         }
+        Consumer<Property> cancelAction = mAnimationCancelAction;
         Consumer<Property> endAction = mAnimationEndAction;
         return new AnimatorListenerAdapter() {
             private boolean mCancelled;
 
             @Override
             public void onAnimationCancel(Animator animation) {
-                 mCancelled = true;
+                mCancelled = true;
+                if (cancelAction != null) {
+                    cancelAction.accept(property);
+                }
             }
 
             @Override
             public void onAnimationEnd(Animator animation) {
-                if (!mCancelled) {
+                if (!mCancelled && endAction != null) {
                     endAction.accept(property);
                 }
             }
         };
     }
 
+    /**
+     * Add a callback for animation cancellation.
+     */
+    public AnimationProperties setAnimationCancelAction(Consumer<Property> listener) {
+        mAnimationCancelAction = listener;
+        return this;
+    }
+
+    /**
+     * Add a callback for animation ending successfully. The callback will not be called when the
+     * animations is cancelled.
+     */
     public AnimationProperties setAnimationEndAction(Consumer<Property> listener) {
         mAnimationEndAction = listener;
         return this;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index e47e414..af608a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -5137,8 +5137,26 @@
         requestChildrenUpdate();
     }
 
+    public void setShelf(NotificationShelf shelf) {
+        if (!NotificationShelfController.checkRefactorFlagEnabled(
+                mAmbientState.getFeatureFlags())) {
+            return;
+        }
+        int index = -1;
+        if (mShelf != null) {
+            index = indexOfChild(mShelf);
+            removeView(mShelf);
+        }
+        mShelf = shelf;
+        addView(mShelf, index);
+        mAmbientState.setShelf(mShelf);
+        mStateAnimator.setShelf(mShelf);
+        shelf.bind(mAmbientState, this, mController.getNotificationRoundnessManager());
+    }
+
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setShelfController(NotificationShelfController notificationShelfController) {
+        NotificationShelfController.assertRefactorFlagDisabled(mAmbientState.getFeatureFlags());
         int index = -1;
         if (mShelf != null) {
             index = indexOfChild(mShelf);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 792746c..1c8727f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -73,6 +73,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -1382,6 +1383,7 @@
     }
 
     public void setShelfController(NotificationShelfController notificationShelfController) {
+        NotificationShelfController.assertRefactorFlagDisabled(mFeatureFlags);
         mView.setShelfController(notificationShelfController);
     }
 
@@ -1593,6 +1595,11 @@
         mView.setOnNotificationRemovedListener(listener);
     }
 
+    public void setShelf(NotificationShelf shelf) {
+        if (!NotificationShelfController.checkRefactorFlagEnabled(mFeatureFlags)) return;
+        mView.setShelf(shelf);
+    }
+
     /**
      * Enum for UiEvent logged from this class
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 0922428..c7cb70c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -23,12 +23,14 @@
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.media.controls.pipeline.MediaDataManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.StatusBarState.KEYGUARD
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.util.Compile
+import com.android.systemui.util.LargeScreenUtils.shouldUseSplitNotificationShade
 import com.android.systemui.util.children
 import java.io.PrintWriter
 import javax.inject.Inject
@@ -51,11 +53,10 @@
 constructor(
     private val statusBarStateController: SysuiStatusBarStateController,
     private val lockscreenShadeTransitionController: LockscreenShadeTransitionController,
+    private val mediaDataManager: MediaDataManager,
     @Main private val resources: Resources
 ) {
 
-    private lateinit var lastComputeHeightLog : String
-
     /**
      * Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow shelf.
      * If there are exactly 1 + mMaxKeyguardNotifications, and they fit in the available space
@@ -67,68 +68,158 @@
     /** Minimum space between two notifications, see [calculateGapAndDividerHeight]. */
     private var dividerHeight by notNull<Float>()
 
+    /**
+     * True when there is not enough vertical space to show at least one notification with heads up
+     * layout. When true, notifications always show collapsed layout.
+     */
+    private var saveSpaceOnLockscreen = false
+
     init {
         updateResources()
     }
 
     /**
      * Returns whether notifications and (shelf if visible) can fit in total space available.
-     * [spaceForShelf] is extra vertical space allowed for the shelf to overlap the lock icon.
+     * [shelfSpace] is extra vertical space allowed for the shelf to overlap the lock icon.
      */
     private fun canStackFitInSpace(
         stackHeight: StackHeight,
-        spaceForNotifications: Float,
-        spaceForShelf: Float,
-    ): Boolean {
-
-        val (notificationsHeight, shelfHeightWithSpaceBefore) = stackHeight
-        var canFit: Boolean
+        notifSpace: Float,
+        shelfSpace: Float,
+    ): FitResult {
+        val (notifHeight, notifHeightSaveSpace, shelfHeightWithSpaceBefore) = stackHeight
 
         if (shelfHeightWithSpaceBefore == 0f) {
-            canFit = notificationsHeight <= spaceForNotifications
-            log {
-                "canStackFitInSpace[$canFit] = notificationsHeight[$notificationsHeight]" +
-                    " <= spaceForNotifications[$spaceForNotifications]"
+            if (notifHeight <= notifSpace) {
+                log {
+                    "\tcanStackFitInSpace[FIT] = notifHeight[$notifHeight]" +
+                        " <= notifSpace[$notifSpace]"
+                }
+                return FitResult.FIT
             }
-        } else {
-            canFit =
-                (notificationsHeight + shelfHeightWithSpaceBefore) <=
-                    (spaceForNotifications + spaceForShelf)
+            if (notifHeightSaveSpace <= notifSpace) {
+                log {
+                    "\tcanStackFitInSpace[FIT_IF_SAVE_SPACE]" +
+                        " = notifHeightSaveSpace[$notifHeightSaveSpace]" +
+                        " <= notifSpace[$notifSpace]"
+                }
+                return FitResult.FIT_IF_SAVE_SPACE
+            }
             log {
-                "canStackFitInSpace[$canFit] = (notificationsHeight[$notificationsHeight]" +
-                    " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" +
-                    " <= (spaceForNotifications[$spaceForNotifications] " +
-                    " + spaceForShelf[$spaceForShelf])"
+                "\tcanStackFitInSpace[NO_FIT]" +
+                    " = notifHeightSaveSpace[$notifHeightSaveSpace] > notifSpace[$notifSpace]"
+            }
+            return FitResult.NO_FIT
+        } else {
+            if ((notifHeight + shelfHeightWithSpaceBefore) <= (notifSpace + shelfSpace)) {
+                log {
+                    "\tcanStackFitInSpace[FIT] = (notifHeight[$notifHeight]" +
+                        " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" +
+                        " <= (notifSpace[$notifSpace] " +
+                        " + spaceForShelf[$shelfSpace])"
+                }
+                return FitResult.FIT
+            } else if (
+                (notifHeightSaveSpace + shelfHeightWithSpaceBefore) <= (notifSpace + shelfSpace)
+            ) {
+                log {
+                    "\tcanStackFitInSpace[FIT_IF_SAVE_SPACE]" +
+                        " = (notifHeightSaveSpace[$notifHeightSaveSpace]" +
+                        " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" +
+                        " <= (notifSpace[$notifSpace] + shelfSpace[$shelfSpace])"
+                }
+                return FitResult.FIT_IF_SAVE_SPACE
+            } else {
+                log {
+                    "\tcanStackFitInSpace[NO_FIT]" +
+                        " = (notifHeightSaveSpace[$notifHeightSaveSpace]" +
+                        " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" +
+                        " > (notifSpace[$notifSpace] + shelfSpace[$shelfSpace])"
+                }
+                return FitResult.NO_FIT
             }
         }
-        return canFit
     }
 
     /**
-     * Given the [spaceForNotifications] and [spaceForShelf] constraints, calculate how many
-     * notifications to show. This number is only valid in keyguard.
+     * Given the [notifSpace] and [shelfSpace] constraints, calculate how many notifications to
+     * show. This number is only valid in keyguard.
      *
      * @param totalAvailableSpace space for notifications. This includes the space for the shelf.
      */
     fun computeMaxKeyguardNotifications(
         stack: NotificationStackScrollLayout,
-        spaceForNotifications: Float,
-        spaceForShelf: Float,
-        shelfIntrinsicHeight: Float
+        notifSpace: Float,
+        shelfSpace: Float,
+        shelfHeight: Float,
     ): Int {
+        log { "\n " }
+        log {
+            "computeMaxKeyguardNotifications ---" +
+                "\n\tnotifSpace $notifSpace" +
+                "\n\tspaceForShelf $shelfSpace" +
+                "\n\tshelfIntrinsicHeight $shelfHeight"
+        }
+        if (notifSpace + shelfSpace <= 0f) {
+            log { "--- No space to show anything. maxNotifs=0" }
+            return 0
+        }
         log { "\n" }
 
-        val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
-            /* computeHeight= */ false)
+        val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfHeight)
+        val isMediaShowing = mediaDataManager.hasActiveMediaOrRecommendation()
 
-        var maxNotifications =
+        log { "\tGet maxNotifWithoutSavingSpace ---" }
+        val maxNotifWithoutSavingSpace =
             stackHeightSequence.lastIndexWhile { heightResult ->
                 canStackFitInSpace(
                     heightResult,
-                    spaceForNotifications = spaceForNotifications,
-                    spaceForShelf = spaceForShelf)
+                    notifSpace = notifSpace,
+                    shelfSpace = shelfSpace
+                ) == FitResult.FIT
             }
 
+        // How many notifications we can show at heightWithoutLockscreenConstraints
+        var minCountAtHeightWithoutConstraints =
+            if (isMediaShowing && !shouldUseSplitNotificationShade(resources)) 2 else 1
+        log {
+            "\t---maxNotifWithoutSavingSpace=$maxNotifWithoutSavingSpace " +
+                "isMediaShowing=$isMediaShowing" +
+                "minCountAtHeightWithoutConstraints=$minCountAtHeightWithoutConstraints"
+        }
+        log { "\n" }
+
+        var maxNotifications: Int
+        if (maxNotifWithoutSavingSpace >= minCountAtHeightWithoutConstraints) {
+            saveSpaceOnLockscreen = false
+            maxNotifications = maxNotifWithoutSavingSpace
+            log {
+                "\tDo NOT save space. maxNotifications=maxNotifWithoutSavingSpace=$maxNotifications"
+            }
+        } else {
+            log { "\tSAVE space ---" }
+            saveSpaceOnLockscreen = true
+            maxNotifications =
+                stackHeightSequence.lastIndexWhile { heightResult ->
+                    canStackFitInSpace(
+                        heightResult,
+                        notifSpace = notifSpace,
+                        shelfSpace = shelfSpace
+                    ) != FitResult.NO_FIT
+                }
+            log { "\t--- maxNotifications=$maxNotifications" }
+        }
+
+        // Must update views immediately to avoid mismatches between initial HUN layout height
+        // and the height adapted to lockscreen space constraints, which causes jump cuts.
+        stack.showableChildren().toList().forEach { currentNotification ->
+            run {
+                if (currentNotification is ExpandableNotificationRow) {
+                    currentNotification.saveSpaceOnLockscreen = saveSpaceOnLockscreen
+                }
+            }
+        }
+
         if (onLockscreen()) {
             maxNotifications = min(maxKeyguardNotifications, maxNotifications)
         }
@@ -137,53 +228,80 @@
         maxNotifications = max(0, maxNotifications)
         log {
             val sequence = if (SPEW) " stackHeightSequence=${stackHeightSequence.toList()}" else ""
-            "computeMaxKeyguardNotifications(" +
-                " spaceForNotifications=$spaceForNotifications" +
-                " spaceForShelf=$spaceForShelf" +
-                " shelfHeight=$shelfIntrinsicHeight) -> $maxNotifications$sequence"
+            "--- computeMaxKeyguardNotifications(" +
+                " notifSpace=$notifSpace" +
+                " shelfSpace=$shelfSpace" +
+                " shelfHeight=$shelfHeight) -> $maxNotifications$sequence"
         }
+        log { "\n" }
         return maxNotifications
     }
 
     /**
-     * Given the [maxNotifications] constraint, calculates the height of the
+     * Given the [maxNotifs] constraint, calculates the height of the
      * [NotificationStackScrollLayout]. This might or might not be in keyguard.
      *
      * @param stack stack containing notifications as children.
-     * @param maxNotifications Maximum number of notifications. When reached, the others will go
-     * into the shelf.
-     * @param shelfIntrinsicHeight height of the shelf, without any padding. It might be zero.
-     *
+     * @param maxNotifs Maximum number of notifications. When reached, the others will go into the
+     *   shelf.
+     * @param shelfHeight height of the shelf, without any padding. It might be zero.
      * @return height of the stack, including shelf height, if needed.
      */
     fun computeHeight(
         stack: NotificationStackScrollLayout,
-        maxNotifications: Int,
-        shelfIntrinsicHeight: Float
+        maxNotifs: Int,
+        shelfHeight: Float
     ): Float {
         log { "\n" }
-        lastComputeHeightLog = ""
-        val heightPerMaxNotifications =
-            computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
-                    /* computeHeight= */ true)
+        log { "computeHeight ---" }
 
-        val (notificationsHeight, shelfHeightWithSpaceBefore) =
-            heightPerMaxNotifications.elementAtOrElse(maxNotifications) {
-                heightPerMaxNotifications.last() // Height with all notifications visible.
+        val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfHeight)
+
+        val (notifsHeight, notifsHeightSavingSpace, shelfHeightWithSpaceBefore) =
+            stackHeightSequence.elementAtOrElse(maxNotifs) {
+                stackHeightSequence.last() // Height with all notifications visible.
             }
-        lastComputeHeightLog += "\ncomputeHeight(maxNotifications=$maxNotifications," +
-                "shelfIntrinsicHeight=$shelfIntrinsicHeight) -> " +
-                "${notificationsHeight + shelfHeightWithSpaceBefore}" +
-                " = ($notificationsHeight + $shelfHeightWithSpaceBefore)"
-        log {
-            lastComputeHeightLog
+
+        var height: Float
+        if (saveSpaceOnLockscreen) {
+            height = notifsHeightSavingSpace + shelfHeightWithSpaceBefore
+            log {
+                "--- computeHeight(maxNotifs=$maxNotifs, shelfHeight=$shelfHeight)" +
+                    " -> $height=($notifsHeightSavingSpace+$shelfHeightWithSpaceBefore)," +
+                    " | saveSpaceOnLockscreen=$saveSpaceOnLockscreen"
+            }
+        } else {
+            height = notifsHeight + shelfHeightWithSpaceBefore
+            log {
+                "--- computeHeight(maxNotifs=$maxNotifs, shelfHeight=$shelfHeight)" +
+                    " -> ${height}=($notifsHeight+$shelfHeightWithSpaceBefore)" +
+                    " | saveSpaceOnLockscreen=$saveSpaceOnLockscreen"
+            }
         }
-        return notificationsHeight + shelfHeightWithSpaceBefore
+        return height
     }
 
+    private enum class FitResult {
+        FIT,
+        FIT_IF_SAVE_SPACE,
+        NO_FIT
+    }
+
+    data class SpaceNeeded(
+        // Float height of spaceNeeded when showing heads up layout for FSI HUNs.
+        val whenEnoughSpace: Float,
+
+        // Float height of space needed when showing collapsed layout for FSI HUNs.
+        val whenSavingSpace: Float
+    )
+
     private data class StackHeight(
         // Float height with ith max notifications (not including shelf)
-        val notificationsHeight: Float,
+        val notifsHeight: Float,
+
+        // Float height with ith max notifications
+        // (not including shelf, using collapsed layout for FSI HUN)
+        val notifsHeightSavingSpace: Float,
 
         // Float height of shelf (0 if shelf is not showing), and space before the shelf that
         // changes during the lockscreen <=> full shade transition.
@@ -193,20 +311,27 @@
     private fun computeHeightPerNotificationLimit(
         stack: NotificationStackScrollLayout,
         shelfHeight: Float,
-        computeHeight: Boolean
     ): Sequence<StackHeight> = sequence {
-        log { "computeHeightPerNotificationLimit" }
-
         val children = stack.showableChildren().toList()
         var notifications = 0f
+        var notifsWithCollapsedHun = 0f
         var previous: ExpandableView? = null
         val onLockscreen = onLockscreen()
 
         // Only shelf. This should never happen, since we allow 1 view minimum (EmptyViewState).
-        yield(StackHeight(notificationsHeight = 0f, shelfHeightWithSpaceBefore = shelfHeight))
+        yield(
+            StackHeight(
+                notifsHeight = 0f,
+                notifsHeightSavingSpace = 0f,
+                shelfHeightWithSpaceBefore = shelfHeight
+            )
+        )
 
         children.forEachIndexed { i, currentNotification ->
-            notifications += spaceNeeded(currentNotification, i, previous, stack, onLockscreen)
+            val space = getSpaceNeeded(currentNotification, i, previous, stack, onLockscreen)
+            notifications += space.whenEnoughSpace
+            notifsWithCollapsedHun += space.whenSavingSpace
+
             previous = currentNotification
 
             val shelfWithSpaceBefore =
@@ -219,22 +344,23 @@
                             stack,
                             previous = currentNotification,
                             current = children[firstViewInShelfIndex],
-                            currentIndex = firstViewInShelfIndex)
+                            currentIndex = firstViewInShelfIndex
+                        )
                     spaceBeforeShelf + shelfHeight
                 }
 
-            val currentLog = "computeHeight | i=$i notificationsHeight=$notifications " +
-                "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
-            if (computeHeight) {
-                lastComputeHeightLog += "\n" + currentLog
-            }
             log {
-                currentLog
+                "\tcomputeHeightPerNotificationLimit i=$i notifs=$notifications " +
+                    "notifsHeightSavingSpace=$notifsWithCollapsedHun" +
+                    " shelfWithSpaceBefore=$shelfWithSpaceBefore"
             }
             yield(
                 StackHeight(
-                    notificationsHeight = notifications,
-                    shelfHeightWithSpaceBefore = shelfWithSpaceBefore))
+                    notifsHeight = notifications,
+                    notifsHeightSavingSpace = notifsWithCollapsedHun,
+                    shelfHeightWithSpaceBefore = shelfWithSpaceBefore
+                )
+            )
         }
     }
 
@@ -256,32 +382,46 @@
     }
 
     @VisibleForTesting
-    fun spaceNeeded(
+    fun getSpaceNeeded(
         view: ExpandableView,
         visibleIndex: Int,
         previousView: ExpandableView?,
         stack: NotificationStackScrollLayout,
-        onLockscreen: Boolean
-    ): Float {
+        onLockscreen: Boolean,
+    ): SpaceNeeded {
         assert(view.isShowable(onLockscreen))
 
+        // Must use heightWithoutLockscreenConstraints because intrinsicHeight references
+        // mSaveSpaceOnLockscreen and using intrinsicHeight here will result in stack overflow.
+        val height = view.heightWithoutLockscreenConstraints.toFloat()
+        val gapAndDividerHeight =
+            calculateGapAndDividerHeight(stack, previousView, current = view, visibleIndex)
+
         var size =
             if (onLockscreen) {
                 if (view is ExpandableNotificationRow && view.entry.isStickyAndNotDemoted) {
-                    view.intrinsicHeight.toFloat()
+                    height
                 } else {
                     view.getMinHeight(/* ignoreTemporaryStates= */ true).toFloat()
                 }
             } else {
-                view.intrinsicHeight.toFloat()
+                height
             }
+        size += gapAndDividerHeight
 
-        size += calculateGapAndDividerHeight(stack, previousView, current = view, visibleIndex)
-        return size
+        var sizeWhenSavingSpace =
+            if (onLockscreen) {
+                view.getMinHeight(/* ignoreTemporaryStates= */ true).toFloat()
+            } else {
+                height
+            }
+        sizeWhenSavingSpace += gapAndDividerHeight
+
+        return SpaceNeeded(size, sizeWhenSavingSpace)
     }
 
     fun dump(pw: PrintWriter, args: Array<out String>) {
-        pw.println("NotificationStackSizeCalculator lastComputeHeightLog = $lastComputeHeightLog")
+        pw.println("NotificationStackSizeCalculator saveSpaceOnLockscreen=$saveSpaceOnLockscreen")
     }
 
     private fun ExpandableView.isShowable(onLockscreen: Boolean): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index 8ee2c6f..74ab47f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.util.Assert
 import com.android.systemui.util.sensors.AsyncSensorManager
@@ -46,6 +47,7 @@
     private val statusBarStateController: StatusBarStateController,
     private val asyncSensorManager: AsyncSensorManager,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor,
     private val dumpManager: DumpManager
 ) : Dumpable, CoreStartable {
 
@@ -72,6 +74,7 @@
             // Not listening anymore since trigger events unregister themselves
             isListening = false
             updateListeningState()
+            keyguardFaceAuthInteractor.onDeviceLifted()
             keyguardUpdateMonitor.requestFaceAuth(
                 FaceAuthApiRequestReason.PICK_UP_GESTURE_TRIGGERED
             )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 118bfc5..0cd3401 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -175,15 +175,19 @@
         // We animate the Y properly separately using the PropertyAnimator, as the panel
         // view also needs to update the end position.
         PropertyAnimator.cancelAnimation(keyguardView, AnimatableProperty.Y)
-        PropertyAnimator.setProperty<View>(keyguardView, AnimatableProperty.Y, currentY,
+        PropertyAnimator.setProperty(keyguardView, AnimatableProperty.Y, currentY,
                 AnimationProperties().setDuration(duration.toLong()),
                 true /* animate */)
 
-        keyguardView.animate()
+        // Cancel any existing CUJs before starting the animation
+        interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
+
+        PropertyAnimator.setProperty(
+            keyguardView, AnimatableProperty.ALPHA, 1f,
+            AnimationProperties()
+                .setDelay(0)
                 .setDuration(duration.toLong())
-                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                .alpha(1f)
-                .withEndAction {
+                .setAnimationEndAction {
                     aodUiAnimationPlaying = false
 
                     // Lock the keyguard if it was waiting for the screen off animation to end.
@@ -199,30 +203,23 @@
                     // Done going to sleep, reset this flag.
                     decidedToAnimateGoingToSleep = null
 
-                    // We need to unset the listener. These are persistent for future animators
-                    keyguardView.animate().setListener(null)
                     interactionJankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
                 }
-                .setListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationCancel(animation: Animator?) {
-                        // If we're cancelled, reset state flags/listeners. The end action above
-                        // will not be called, which is what we want since that will finish the
-                        // screen off animation and show the lockscreen, which we don't want if we
-                        // were cancelled.
-                        aodUiAnimationPlaying = false
-                        decidedToAnimateGoingToSleep = null
-                        keyguardView.animate().setListener(null)
-
-                        interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
-                    }
-
-                    override fun onAnimationStart(animation: Animator?) {
-                        interactionJankMonitor.begin(
-                                mCentralSurfaces.notificationShadeWindowView,
-                                CUJ_SCREEN_OFF_SHOW_AOD)
-                    }
-                })
-                .start()
+                .setAnimationCancelAction {
+                    // If we're cancelled, reset state flags/listeners. The end action above
+                    // will not be called, which is what we want since that will finish the
+                    // screen off animation and show the lockscreen, which we don't want if we
+                    // were cancelled.
+                    aodUiAnimationPlaying = false
+                    decidedToAnimateGoingToSleep = null
+                    interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
+                }
+                .setCustomInterpolator(View.ALPHA, Interpolators.FAST_OUT_SLOW_IN),
+            true /* animate */)
+        interactionJankMonitor.begin(
+            mCentralSurfaces.notificationShadeWindowView,
+            CUJ_SCREEN_OFF_SHOW_AOD
+        )
     }
 
     override fun onStartedWakingUp() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index 075e6ec..a05ab84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -26,15 +26,7 @@
 import java.io.PrintWriter
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
 /**
@@ -46,39 +38,16 @@
  * the list of available mobile lines of service for which we want to show icons.
  */
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class MobileUiAdapter
 @Inject
 constructor(
-    interactor: MobileIconsInteractor,
     private val iconController: StatusBarIconController,
-    private val iconsViewModelFactory: MobileIconsViewModel.Factory,
+    val mobileIconsViewModel: MobileIconsViewModel,
     private val logger: MobileViewLogger,
     @Application private val scope: CoroutineScope,
     private val statusBarPipelineFlags: StatusBarPipelineFlags,
 ) : CoreStartable {
-    private val mobileSubIds: Flow<List<Int>> =
-        interactor.filteredSubscriptions.mapLatest { subscriptions ->
-            subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
-        }
-
-    /**
-     * We expose the list of tracked subscriptions as a flow of a list of ints, where each int is
-     * the subscriptionId of the relevant subscriptions. These act as a key into the layouts which
-     * house the mobile infos.
-     *
-     * NOTE: this should go away as the view presenter learns more about this data pipeline
-     */
-    private val mobileSubIdsState: StateFlow<List<Int>> =
-        mobileSubIds
-            .distinctUntilChanged()
-            .onEach { logger.logUiAdapterSubIdsUpdated(it) }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
-
-    /** In order to keep the logs tame, we will reuse the same top-level mobile icons view model */
-    val mobileIconsViewModel = iconsViewModelFactory.create(mobileSubIdsState)
-
     private var isCollecting: Boolean = false
     private var lastValue: List<Int>? = null
 
@@ -90,7 +59,7 @@
         if (statusBarPipelineFlags.useNewMobileIcons()) {
             scope.launch {
                 isCollecting = true
-                mobileSubIds.collectLatest {
+                mobileIconsViewModel.subscriptionIdsFlow.collectLatest {
                     logger.logUiAdapterSubIdsSentToIconController(it)
                     lastValue = it
                     iconController.setNewMobileIconSubIds(it)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
index 90dff23..f2f9143 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
@@ -41,15 +41,6 @@
 
     private val collectionStatuses = mutableMapOf<String, Boolean>()
 
-    fun logUiAdapterSubIdsUpdated(subs: List<Int>) {
-        buffer.log(
-            TAG,
-            LogLevel.INFO,
-            { str1 = subs.toString() },
-            { "Sub IDs in MobileUiAdapter updated internally: $str1" },
-        )
-    }
-
     fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) {
         buffer.log(
             TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 2b90065..9af5e83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -29,18 +29,23 @@
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
 /**
  * View model for describing the system's current mobile cellular connections. The result is a list
  * of [MobileIconViewModel]s which describe the individual icons and can be bound to
- * [ModernStatusBarMobileView]
+ * [ModernStatusBarMobileView].
  */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
 class MobileIconsViewModel
 @Inject
 constructor(
-    val subscriptionIdsFlow: StateFlow<List<Int>>,
     val logger: MobileViewLogger,
     private val verboseLogger: VerboseMobileViewLogger,
     private val interactor: MobileIconsInteractor,
@@ -51,6 +56,13 @@
 ) {
     @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>()
 
+    val subscriptionIdsFlow: StateFlow<List<Int>> =
+        interactor.filteredSubscriptions
+            .mapLatest { subscriptions ->
+                subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+
     init {
         scope.launch { subscriptionIdsFlow.collect { removeInvalidModelsFromCache(it) } }
     }
@@ -79,30 +91,4 @@
         val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) }
         subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) }
     }
-
-    @SysUISingleton
-    class Factory
-    @Inject
-    constructor(
-        private val logger: MobileViewLogger,
-        private val verboseLogger: VerboseMobileViewLogger,
-        private val interactor: MobileIconsInteractor,
-        private val airplaneModeInteractor: AirplaneModeInteractor,
-        private val constants: ConnectivityConstants,
-        @Application private val scope: CoroutineScope,
-        private val statusBarPipelineFlags: StatusBarPipelineFlags,
-    ) {
-        fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel {
-            return MobileIconsViewModel(
-                subscriptionIdsFlow,
-                logger,
-                verboseLogger,
-                interactor,
-                airplaneModeInteractor,
-                constants,
-                scope,
-                statusBarPipelineFlags,
-            )
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index e9f0dcb..928e011 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -61,6 +61,7 @@
  * Manages the user switcher on the Keyguard.
  */
 @KeyguardUserSwitcherScope
+@Deprecated
 public class KeyguardUserSwitcherController extends ViewController<KeyguardUserSwitcherView> {
 
     private static final String TAG = "KeyguardUserSwitcherController";
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index a20a5b2..e819f94 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -31,6 +31,7 @@
 import android.view.accessibility.AccessibilityManager
 import android.widget.ImageView
 import android.widget.TextView
+import androidx.annotation.DimenRes
 import androidx.annotation.IdRes
 import androidx.annotation.VisibleForTesting
 import com.android.internal.widget.CachingIconView
@@ -180,8 +181,9 @@
 
         // Button
         val buttonView = currentView.requireViewById<TextView>(R.id.end_button)
-        if (newInfo.endItem is ChipbarEndItem.Button) {
-            TextViewBinder.bind(buttonView, newInfo.endItem.text)
+        val hasButton = newInfo.endItem is ChipbarEndItem.Button
+        if (hasButton) {
+            TextViewBinder.bind(buttonView, (newInfo.endItem as ChipbarEndItem.Button).text)
 
             val onClickListener =
                 View.OnClickListener { clickedView ->
@@ -196,6 +198,12 @@
             buttonView.visibility = View.GONE
         }
 
+        currentView
+            .getInnerView()
+            .setEndPadding(
+                if (hasButton) R.dimen.chipbar_outer_padding_half else R.dimen.chipbar_outer_padding
+            )
+
         // ---- Overall accessibility ----
         val iconDesc = newInfo.startIcon.icon.contentDescription
         val loadedIconDesc =
@@ -309,6 +317,15 @@
         viewUtil.setRectToViewWindowLocation(view, outRect)
     }
 
+    private fun View.setEndPadding(@DimenRes endPaddingDimen: Int) {
+        this.setPaddingRelative(
+            this.paddingStart,
+            this.paddingTop,
+            context.resources.getDimensionPixelSize(endPaddingDimen),
+            this.paddingBottom,
+        )
+    }
+
     private fun Boolean.visibleIfTrue(): Int {
         return if (this) {
             View.VISIBLE
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
index 6e58f22..52f2d11 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -18,7 +18,7 @@
 
 import android.os.VibrationEffect
 import android.view.View
-import androidx.annotation.ColorRes
+import androidx.annotation.AttrRes
 import com.android.systemui.R
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.shared.model.TintedIcon
@@ -49,7 +49,9 @@
     override val priority: ViewPriority,
 ) : TemporaryViewInfo() {
     companion object {
-        @ColorRes val DEFAULT_ICON_TINT = R.color.chipbar_text_and_icon_color
+        // LINT.IfChange
+        @AttrRes val DEFAULT_ICON_TINT = com.android.internal.R.attr.materialColorOnSecondaryFixed
+        // LINT.ThenChange(systemui/res/layout/chipbar.xml)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
index 05e5666..29f16c7 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
@@ -272,10 +272,10 @@
 
     private static boolean showApplicationIcon(ApplicationInfo appInfo,
             PackageManager packageManager) {
-        if (hasFlag(appInfo.flags, FLAG_UPDATED_SYSTEM_APP)) {
+        if (hasFlag(appInfo.flags, FLAG_UPDATED_SYSTEM_APP | FLAG_SYSTEM)) {
             return packageManager.getLaunchIntentForPackage(appInfo.packageName) != null;
         }
-        return !hasFlag(appInfo.flags, FLAG_SYSTEM);
+        return true;
     }
 
     private static boolean hasFlag(int flags, int flag) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
new file mode 100644
index 0000000..c587f2e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import dagger.Lazy
+import kotlin.reflect.KProperty
+
+/**
+ * Extension operator that allows developers to use [dagger.Lazy] as a property delegate:
+ * ```kotlin
+ *    class MyClass @Inject constructor(
+ *      lazyDependency: dagger.Lazy<Foo>,
+ *    ) {
+ *      val dependency: Foo by lazyDependency
+ *    }
+ * ```
+ */
+operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = get()
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index 209ea41..58cffa7 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -58,6 +58,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -418,6 +419,7 @@
         @Inject
         public MemoryTile(
                 QSHost host,
+                QsEventLogger uiEventLogger,
                 @Background Looper backgroundLooper,
                 @Main Handler mainHandler,
                 FalsingManager falsingManager,
@@ -428,7 +430,7 @@
                 GarbageMonitor monitor,
                 PanelInteractor panelInteractor
         ) {
-            super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+            super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                     statusBarStateController, activityStarter, qsLogger);
             gm = monitor;
             mPanelInteractor = panelInteractor;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
index 35af444..e3ed2b4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
@@ -16,20 +16,34 @@
 
 package com.android.systemui.volume;
 
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
+
 import android.annotation.StringRes;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.media.AudioManager;
-import android.os.CountDownTimer;
+import android.provider.Settings;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.WindowManager;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.util.NotificationChannels;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
 
 /**
  * A class that implements the four Computed Sound Dose-related warnings defined in {@link AudioManager}:
@@ -53,34 +67,58 @@
  * communication between the native audio framework that implements the dose computation and the
  * audio service.
  */
-public abstract class CsdWarningDialog extends SystemUIDialog
+public class CsdWarningDialog extends SystemUIDialog
         implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
 
     private static final String TAG = Util.logTag(CsdWarningDialog.class);
 
     private static final int KEY_CONFIRM_ALLOWED_AFTER_MS = 1000; // milliseconds
     // time after which action is taken when the user hasn't ack'd or dismissed the dialog
-    private static final int NO_ACTION_TIMEOUT_MS = 5000;
+    public static final int NO_ACTION_TIMEOUT_MS = 5000;
 
     private final Context mContext;
     private final AudioManager mAudioManager;
     private final @AudioManager.CsdWarning int mCsdWarning;
     private final Object mTimerLock = new Object();
+
     /**
      * Timer to keep track of how long the user has before an action (here volume reduction) is
      * taken on their behalf.
      */
     @GuardedBy("mTimerLock")
-    private final CountDownTimer mNoUserActionTimer;
+    private Runnable mNoUserActionRunnable;
+    private Runnable mCancelScheduledNoUserActionRunnable = null;
+
+    private final DelayableExecutor mDelayableExecutor;
+    private NotificationManager mNotificationManager;
+    private Runnable mOnCleanup;
 
     private long mShowTime;
 
-    public CsdWarningDialog(@AudioManager.CsdWarning int csdWarning, Context context,
-            AudioManager audioManager) {
+    /**
+     * To inject dependencies and allow for easier testing
+     */
+    @AssistedFactory
+    public interface Factory {
+        /**
+         * Create a dialog object
+         */
+        CsdWarningDialog create(int csdWarning, Runnable onCleanup);
+    }
+
+    @AssistedInject
+    public CsdWarningDialog(@Assisted @AudioManager.CsdWarning int csdWarning, Context context,
+            AudioManager audioManager, NotificationManager notificationManager,
+            @Background DelayableExecutor delayableExecutor, @Assisted Runnable onCleanup) {
         super(context);
         mCsdWarning = csdWarning;
         mContext = context;
         mAudioManager = audioManager;
+        mNotificationManager = notificationManager;
+        mOnCleanup = onCleanup;
+
+        mDelayableExecutor = delayableExecutor;
+
         getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
         setShowForAllUsers(true);
         setMessage(mContext.getString(getStringForWarning(csdWarning)));
@@ -95,25 +133,24 @@
                 Context.RECEIVER_EXPORTED_UNAUDITED);
 
         if (csdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) {
-            mNoUserActionTimer = new CountDownTimer(NO_ACTION_TIMEOUT_MS, NO_ACTION_TIMEOUT_MS) {
-                @Override
-                public void onTick(long millisUntilFinished) { }
-
-                @Override
-                public void onFinish() {
-                    if (mCsdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) {
-                        // unlike on the 5x dose repeat, level is only reduced to RS1
-                        // when the warning is not acknowledged quick enough
-                        mAudioManager.lowerVolumeToRs1();
-                    }
+            mNoUserActionRunnable = () -> {
+                if (mCsdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) {
+                    // unlike on the 5x dose repeat, level is only reduced to RS1 when the warning
+                    // is not acknowledged quickly enough
+                    mAudioManager.lowerVolumeToRs1();
+                    sendNotification();
                 }
             };
         } else {
-            mNoUserActionTimer = null;
+            mNoUserActionRunnable = null;
         }
     }
 
-    protected abstract void cleanUp();
+    private void cleanUp() {
+        if (mOnCleanup != null) {
+            mOnCleanup.run();
+        }
+    }
 
     // NOT overriding onKeyDown as we're not allowing a dismissal on any key other than
     // VOLUME_DOWN, and for this, we don't need to track if it's the start of a new
@@ -153,12 +190,9 @@
         super.onStart();
         mShowTime = System.currentTimeMillis();
         synchronized (mTimerLock) {
-            if (mNoUserActionTimer != null) {
-                new Thread(() -> {
-                    synchronized (mTimerLock) {
-                        mNoUserActionTimer.start();
-                    }
-                }).start();
+            if (mNoUserActionRunnable != null) {
+                mCancelScheduledNoUserActionRunnable = mDelayableExecutor.executeDelayed(
+                        mNoUserActionRunnable, NO_ACTION_TIMEOUT_MS);
             }
         }
     }
@@ -166,8 +200,8 @@
     @Override
     protected void onStop() {
         synchronized (mTimerLock) {
-            if (mNoUserActionTimer != null) {
-                mNoUserActionTimer.cancel();
+            if (mCancelScheduledNoUserActionRunnable != null) {
+                mCancelScheduledNoUserActionRunnable.run();
             }
         }
     }
@@ -212,4 +246,32 @@
         Log.e(TAG, "Invalid CSD warning event " + csdWarning, new Exception());
         return com.android.internal.R.string.csd_dose_reached_warning;
     }
+
+
+    /**
+     * In case user did not respond to the dialog, they still need to know volume was lowered.
+     */
+    private void sendNotification() {
+        Intent intent = new Intent(Settings.ACTION_SOUND_SETTINGS);
+        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent,
+                FLAG_IMMUTABLE);
+
+        String text = mContext.getString(R.string.csd_system_lowered_text);
+        String title = mContext.getString(R.string.csd_lowered_title);
+
+        Notification.Builder builder =
+                new Notification.Builder(mContext, NotificationChannels.ALERTS)
+                        .setSmallIcon(R.drawable.hearing)
+                        .setContentTitle(title)
+                        .setContentText(text)
+                        .setContentIntent(pendingIntent)
+                        .setStyle(new Notification.BigTextStyle().bigText(text))
+                        .setVisibility(Notification.VISIBILITY_PUBLIC)
+                        .setLocalOnly(true)
+                        .setAutoCancel(true)
+                        .setCategory(Notification.CATEGORY_SYSTEM);
+
+        mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO,
+                builder.build());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 95cc12a..3c007f9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -263,6 +263,7 @@
     private final ConfigurationController mConfigurationController;
     private final MediaOutputDialogFactory mMediaOutputDialogFactory;
     private final VolumePanelFactory mVolumePanelFactory;
+    private final CsdWarningDialog.Factory mCsdWarningDialogFactory;
     private final ActivityStarter mActivityStarter;
 
     private boolean mShowing;
@@ -311,6 +312,7 @@
             InteractionJankMonitor interactionJankMonitor,
             DeviceConfigProxy deviceConfigProxy,
             Executor executor,
+            CsdWarningDialog.Factory csdWarningDialogFactory,
             DumpManager dumpManager) {
         mContext =
                 new ContextThemeWrapper(context, R.style.volume_dialog_theme);
@@ -322,6 +324,7 @@
         mConfigurationController = configurationController;
         mMediaOutputDialogFactory = mediaOutputDialogFactory;
         mVolumePanelFactory = volumePanelFactory;
+        mCsdWarningDialogFactory = csdWarningDialogFactory;
         mActivityStarter = activityStarter;
         mShowActiveStreamOnly = showActiveStreamOnly();
         mHasSeenODICaptionsTooltip =
@@ -2084,21 +2087,21 @@
         rescheduleTimeoutH();
     }
 
-    private void showCsdWarningH(int csdWarning, int durationMs) {
+    @VisibleForTesting void showCsdWarningH(int csdWarning, int durationMs) {
         synchronized (mSafetyWarningLock) {
+
             if (mCsdDialog != null) {
                 return;
             }
-            mCsdDialog = new CsdWarningDialog(csdWarning,
-                    mContext, mController.getAudioManager()) {
-                @Override
-                protected void cleanUp() {
-                    synchronized (mSafetyWarningLock) {
-                        mCsdDialog = null;
-                    }
-                    recheckH(null);
+
+            final Runnable cleanUp = () -> {
+                synchronized (mSafetyWarningLock) {
+                    mCsdDialog = null;
                 }
+                recheckH(null);
             };
+
+            mCsdDialog = mCsdWarningDialogFactory.create(csdWarning, cleanUp);
             mCsdDialog.show();
         }
         recheckH(null);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 0ab6c69..14d3ca3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -30,17 +30,18 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.volume.CsdWarningDialog;
 import com.android.systemui.volume.VolumeComponent;
 import com.android.systemui.volume.VolumeDialogComponent;
 import com.android.systemui.volume.VolumeDialogImpl;
 import com.android.systemui.volume.VolumePanelFactory;
 
-import java.util.concurrent.Executor;
-
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.concurrent.Executor;
+
 
 /** Dagger Module for code in the volume package. */
 @Module
@@ -63,6 +64,7 @@
             InteractionJankMonitor interactionJankMonitor,
             DeviceConfigProxy deviceConfigProxy,
             @Main Executor executor,
+            CsdWarningDialog.Factory csdFactory,
             DumpManager dumpManager) {
         VolumeDialogImpl impl = new VolumeDialogImpl(
                 context,
@@ -76,6 +78,7 @@
                 interactionJankMonitor,
                 deviceConfigProxy,
                 executor,
+                csdFactory,
                 dumpManager);
         impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
         impl.setAutomute(true);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index d760189..3e4fd89 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -67,6 +67,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
@@ -208,7 +209,8 @@
                 mConfigurationController, mFalsingCollector, mFalsingManager,
                 mUserSwitcherController, mFeatureFlags, mGlobalSettings,
                 mSessionTracker, Optional.of(mSideFpsController), mFalsingA11yDelegate,
-                mTelephonyManager, mViewMediatorCallback, mAudioManager);
+                mTelephonyManager, mViewMediatorCallback, mAudioManager,
+                mock(KeyguardFaceAuthInteractor.class));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index f1ee108..2c1d2ad 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -16,7 +16,9 @@
 
 package com.android.keyguard;
 
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -25,6 +27,9 @@
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.plugins.ClockConfig;
+import com.android.systemui.plugins.ClockController;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -76,7 +81,16 @@
                 mScreenOffAnimationController,
                 mKeyguardLogger,
                 mFeatureFlags,
-                mInteractionJankMonitor);
+                mInteractionJankMonitor) {
+                    @Override
+                    void setProperty(
+                            AnimatableProperty property,
+                            float value,
+                            boolean animate) {
+                        // Route into the mock version for verification
+                        mControllerMock.setProperty(property, value, animate);
+                    }
+                };
     }
 
     @Test
@@ -111,4 +125,34 @@
         configurationListenerArgumentCaptor.getValue().onLocaleListChanged();
         verify(mKeyguardClockSwitchController).onLocaleListChanged();
     }
+
+    @Test
+    public void updatePosition_primaryClockAnimation() {
+        ClockController mockClock = mock(ClockController.class);
+        when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
+        when(mockClock.getConfig()).thenReturn(new ClockConfig(false, false, true));
+
+        mController.updatePosition(10, 15, 20f, true);
+
+        verify(mControllerMock).setProperty(AnimatableProperty.Y, 15f, true);
+        verify(mKeyguardClockSwitchController).updatePosition(
+                10, 20f, KeyguardStatusViewController.CLOCK_ANIMATION_PROPERTIES, true);
+        verify(mControllerMock).setProperty(AnimatableProperty.SCALE_X, 1f, true);
+        verify(mControllerMock).setProperty(AnimatableProperty.SCALE_Y, 1f, true);
+    }
+
+    @Test
+    public void updatePosition_alternateClockAnimation() {
+        ClockController mockClock = mock(ClockController.class);
+        when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
+        when(mockClock.getConfig()).thenReturn(new ClockConfig(false, true, true));
+
+        mController.updatePosition(10, 15, 20f, true);
+
+        verify(mControllerMock).setProperty(AnimatableProperty.Y, 15f, true);
+        verify(mKeyguardClockSwitchController).updatePosition(
+                10, 1f, KeyguardStatusViewController.CLOCK_ANIMATION_PROPERTIES, true);
+        verify(mControllerMock).setProperty(AnimatableProperty.SCALE_X, 20f, true);
+        verify(mControllerMock).setProperty(AnimatableProperty.SCALE_Y, 20f, true);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 0978c82..9c36af3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -18,6 +18,7 @@
 
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
 import static android.view.Choreographer.FrameCallback;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
@@ -172,6 +173,12 @@
                 returnsSecondArg());
 
         mResources = getContext().getOrCreateTestableResources().getResources();
+        // prevent the config orientation from undefined, which may cause config.diff method
+        // neglecting the orientation update.
+        if (mResources.getConfiguration().orientation == ORIENTATION_UNDEFINED) {
+            mResources.getConfiguration().orientation = ORIENTATION_PORTRAIT;
+        }
+
         mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
                 mContext, mValueAnimator);
         mWindowMagnificationController =
@@ -688,7 +695,11 @@
 
     @Test
     public void enableWindowMagnification_rotationIsChanged_updateRotationValue() {
-        final Configuration config = mContext.getResources().getConfiguration();
+        // the config orientation should not be undefined, since it would cause config.diff
+        // returning 0 and thus the orientation changed would not be detected
+        assertNotEquals(ORIENTATION_UNDEFINED, mResources.getConfiguration().orientation);
+
+        final Configuration config = mResources.getConfiguration();
         config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT
                 : ORIENTATION_LANDSCAPE;
         final int newRotation = simulateRotateTheDevice();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
index 921f9a8..2b95973 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
@@ -37,7 +37,13 @@
 
     @Test
     fun onBackProgressed_shouldInvoke_onBackProgress() {
-        val backEvent = BackEvent(0f, 0f, 0f, BackEvent.EDGE_LEFT)
+        val backEvent =
+            BackEvent(
+                /* touchX = */ 0f,
+                /* touchY = */ 0f,
+                /* progress = */ 0f,
+                /* swipeEdge = */ BackEvent.EDGE_LEFT
+            )
         onBackAnimationCallback.onBackStarted(backEvent)
 
         onBackAnimationCallback.onBackProgressed(backEvent)
@@ -47,7 +53,13 @@
 
     @Test
     fun onBackStarted_shouldInvoke_onBackStart() {
-        val backEvent = BackEvent(0f, 0f, 0f, BackEvent.EDGE_LEFT)
+        val backEvent =
+            BackEvent(
+                /* touchX = */ 0f,
+                /* touchY = */ 0f,
+                /* progress = */ 0f,
+                /* swipeEdge = */ BackEvent.EDGE_LEFT
+            )
 
         onBackAnimationCallback.onBackStarted(backEvent)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index b41053c..ef750be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -51,32 +51,37 @@
     @Test
     fun testEnableDetector_expandWithTrack_shouldPostRunnable() {
         detector.enable(action)
-        // simulate notification expand
-        shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, true, 5566f)
+        shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, true, 0f)
         verify(action).run()
     }
 
     @Test
     fun testEnableDetector_trackOnly_shouldPostRunnable() {
         detector.enable(action)
-        // simulate notification expand
-        shadeExpansionStateManager.onPanelExpansionChanged(5566f, false, true, 5566f)
+        shadeExpansionStateManager.onPanelExpansionChanged(1.0f, false, true, 0f)
         verify(action).run()
     }
 
     @Test
     fun testEnableDetector_expandOnly_shouldPostRunnable() {
         detector.enable(action)
-        // simulate notification expand
-        shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, false, 5566f)
+        shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, false, 0f)
         verify(action).run()
     }
 
     @Test
+    fun testEnableDetector_expandWithoutFraction_shouldPostRunnable() {
+        detector.enable(action)
+        // simulate headsup notification
+        shadeExpansionStateManager.onPanelExpansionChanged(0.0f, true, false, 0f)
+        verifyZeroInteractions(action)
+    }
+
+    @Test
     fun testEnableDetector_shouldNotPostRunnable() {
         detector.enable(action)
         detector.disable()
-        shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, true, 5566f)
+        shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, true, 0f)
         verifyZeroInteractions(action)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 64c028e..eae95a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -87,6 +87,7 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
@@ -311,7 +312,8 @@
                 mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker,
                 mActivityLaunchAnimator, alternateTouchProvider, mBiometricExecutor,
                 mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker,
-                mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils);
+                mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils,
+                mock(KeyguardFaceAuthInteractor.class));
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
         verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
index 08427da..21397d97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
@@ -269,6 +269,30 @@
     }
 
     @Test
+    public void testInputEventPropagationAfterRemoval() {
+        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+
+        final Environment environment = new Environment(Stream.of(touchHandler)
+                .collect(Collectors.toCollection(HashSet::new)));
+
+        final InputEvent initialEvent = Mockito.mock(InputEvent.class);
+        environment.publishInputEvent(initialEvent);
+
+        // Ensure session started
+        final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
+        final InputChannelCompat.InputEventListener eventListener =
+                registerInputEventListener(session);
+
+        session.pop();
+        environment.executeAll();
+
+        final InputEvent event = Mockito.mock(InputEvent.class);
+        environment.publishInputEvent(event);
+
+        verify(eventListener, never()).onInputEvent(eq(event));
+    }
+
+    @Test
     public void testInputGesturePropagation() {
         final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 2489e04..f21ea3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -54,6 +54,7 @@
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.log.SessionTracker
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.phone.FakeKeyguardStateController
 import com.android.systemui.statusbar.phone.KeyguardBypassController
@@ -61,8 +62,11 @@
 import com.android.systemui.util.mockito.KotlinArgumentCaptor
 import com.android.systemui.util.mockito.captureMany
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.time.SystemClock
 import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.StandardTestDispatcher
@@ -88,8 +92,6 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.MockitoAnnotations
-import java.io.PrintWriter
-import java.io.StringWriter
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -183,8 +185,11 @@
     private fun createDeviceEntryFaceAuthRepositoryImpl(
         fmOverride: FaceManager? = faceManager,
         bypassControllerOverride: KeyguardBypassController? = bypassController
-    ) =
-        DeviceEntryFaceAuthRepositoryImpl(
+    ): DeviceEntryFaceAuthRepositoryImpl {
+        val systemClock = FakeSystemClock()
+        val faceAuthBuffer = TableLogBuffer(10, "face auth", systemClock)
+        val faceDetectBuffer = TableLogBuffer(10, "face detect", systemClock)
+        return DeviceEntryFaceAuthRepositoryImpl(
             mContext,
             fmOverride,
             fakeUserRepository,
@@ -200,8 +205,11 @@
             keyguardRepository,
             keyguardInteractor,
             alternateBouncerInteractor,
+            faceDetectBuffer,
+            faceAuthBuffer,
             dumpManager,
         )
+    }
 
     @Test
     fun faceAuthRunsAndProvidesAuthStatusUpdates() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
new file mode 100644
index 0000000..3d1d2f4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -0,0 +1,292 @@
+/*
+ *  Copyright (C) 2023 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.os.Handler
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.log.FaceAuthenticationLogger
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: SystemUIKeyguardFaceAuthInteractor
+    private lateinit var testScope: TestScope
+    private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
+    private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+    private lateinit var faceAuthRepository: FakeDeviceEntryFaceAuthRepository
+
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        val scheduler = TestCoroutineScheduler()
+        val dispatcher = StandardTestDispatcher(scheduler)
+        testScope = TestScope(dispatcher)
+        val featureFlags = FakeFeatureFlags()
+        featureFlags.set(Flags.FACE_AUTH_REFACTOR, true)
+        bouncerRepository = FakeKeyguardBouncerRepository()
+        faceAuthRepository = FakeDeviceEntryFaceAuthRepository()
+        keyguardTransitionRepository = FakeKeyguardTransitionRepository()
+        keyguardTransitionInteractor = KeyguardTransitionInteractor(keyguardTransitionRepository)
+
+        underTest =
+            SystemUIKeyguardFaceAuthInteractor(
+                testScope.backgroundScope,
+                dispatcher,
+                faceAuthRepository,
+                PrimaryBouncerInteractor(
+                    bouncerRepository,
+                    mock(BouncerView::class.java),
+                    mock(Handler::class.java),
+                    mock(KeyguardStateController::class.java),
+                    mock(KeyguardSecurityModel::class.java),
+                    mock(PrimaryBouncerCallbackInteractor::class.java),
+                    mock(FalsingCollector::class.java),
+                    mock(DismissCallbackRegistry::class.java),
+                    context,
+                    keyguardUpdateMonitor,
+                    mock(KeyguardBypassController::class.java),
+                ),
+                AlternateBouncerInteractor(
+                    mock(StatusBarStateController::class.java),
+                    mock(KeyguardStateController::class.java),
+                    bouncerRepository,
+                    mock(BiometricSettingsRepository::class.java),
+                    FakeDeviceEntryFingerprintAuthRepository(),
+                    FakeSystemClock(),
+                ),
+                keyguardTransitionInteractor,
+                featureFlags,
+                FaceAuthenticationLogger(logcatLogBuffer("faceAuthBuffer")),
+                keyguardUpdateMonitor,
+            )
+    }
+
+    @Test
+    fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromOffState() =
+        testScope.runTest {
+            underTest.start()
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    KeyguardState.OFF,
+                    KeyguardState.LOCKSCREEN,
+                    transitionState = TransitionState.STARTED
+                )
+            )
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(
+                    Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, true)
+                )
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromAodState() =
+        testScope.runTest {
+            underTest.start()
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    KeyguardState.AOD,
+                    KeyguardState.LOCKSCREEN,
+                    transitionState = TransitionState.STARTED
+                )
+            )
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(
+                    Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, true)
+                )
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromDozingState() =
+        testScope.runTest {
+            underTest.start()
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    KeyguardState.DOZING,
+                    KeyguardState.LOCKSCREEN,
+                    transitionState = TransitionState.STARTED
+                )
+            )
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(
+                    Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, true)
+                )
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenPrimaryBouncerIsVisible() =
+        testScope.runTest {
+            underTest.start()
+
+            bouncerRepository.setPrimaryShow(false)
+            runCurrent()
+
+            bouncerRepository.setPrimaryShow(true)
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN, true))
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenAlternateBouncerIsVisible() =
+        testScope.runTest {
+            underTest.start()
+
+            bouncerRepository.setAlternateVisible(false)
+            runCurrent()
+
+            bouncerRepository.setAlternateVisible(true)
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(
+                    Pair(
+                        FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN,
+                        false
+                    )
+                )
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenUdfpsSensorTouched() =
+        testScope.runTest {
+            underTest.start()
+
+            underTest.onUdfpsSensorTouched()
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN, false))
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenOnAssistantTriggeredOnLockScreen() =
+        testScope.runTest {
+            underTest.start()
+
+            underTest.onAssistantTriggeredOnLockScreen()
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(
+                    Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED, true)
+                )
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenDeviceLifted() =
+        testScope.runTest {
+            underTest.start()
+
+            underTest.onDeviceLifted()
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(
+                    Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED, true)
+                )
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenQsExpansionStared() =
+        testScope.runTest {
+            underTest.start()
+
+            underTest.onQsExpansionStared()
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, true))
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenNotificationPanelClicked() =
+        testScope.runTest {
+            underTest.start()
+
+            underTest.onNotificationPanelClicked()
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(
+                    Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, true)
+                )
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenSwipeUpOnBouncer() =
+        testScope.runTest {
+            underTest.start()
+
+            underTest.onSwipeUpOnBouncer()
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false))
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index aa92177..e4d8b25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -46,7 +46,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
 import com.android.internal.util.CollectionUtils;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
@@ -117,8 +116,6 @@
     @Mock
     private CustomTile mCustomTile;
     @Mock
-    private UiEventLogger mUiEventLogger;
-    @Mock
     private UserTracker mUserTracker;
     private SecureSettings mSecureSettings;
     @Mock
@@ -164,7 +161,7 @@
         saveSetting("");
         mQSTileHost = new TestQSTileHost(mContext, mDefaultFactory, mMainExecutor,
                 mPluginManager, mTunerService, mAutoTiles, mCentralSurfaces,
-                mQSLogger, mUiEventLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
+                mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
                 mTileLifecycleManagerFactory, mUserFileManager, mFeatureFlags);
 
         mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) {
@@ -684,14 +681,14 @@
                 QSFactory defaultFactory, Executor mainExecutor,
                 PluginManager pluginManager, TunerService tunerService,
                 Provider<AutoTileManager> autoTiles,
-                CentralSurfaces centralSurfaces, QSLogger qsLogger, UiEventLogger uiEventLogger,
+                CentralSurfaces centralSurfaces, QSLogger qsLogger,
                 UserTracker userTracker, SecureSettings secureSettings,
                 CustomTileStatePersister customTileStatePersister,
                 TileLifecycleManager.Factory tileLifecycleManagerFactory,
                 UserFileManager userFileManager, FeatureFlags featureFlags) {
             super(context, defaultFactory, mainExecutor, pluginManager,
                     tunerService, autoTiles,  Optional.of(centralSurfaces), qsLogger,
-                    uiEventLogger, userTracker, secureSettings, customTileStatePersister,
+                    userTracker, secureSettings, customTileStatePersister,
                     tileLifecycleManagerFactory, userFileManager, featureFlags);
         }
 
@@ -710,6 +707,7 @@
         protected TestTile(QSHost host) {
             super(
                     host,
+                    mock(QsEventLogger.class),
                     mock(Looper.class),
                     mock(Handler.class),
                     new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt
new file mode 100644
index 0000000..40aa260
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.InstanceIdSequenceFake
+
+class QsEventLoggerFake(
+    uiEventLogger: UiEventLoggerFake,
+    private val instanceIdSequence: InstanceIdSequenceFake,
+) : QsEventLogger, UiEventLogger by uiEventLogger {
+
+    val lastInstanceId: Int
+        get() = instanceIdSequence.lastInstanceId
+
+    override fun getNewInstanceId(): InstanceId {
+        return instanceIdSequence.newInstanceId()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index ac106ef..198ed4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -41,6 +41,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.settings.FakeDisplayTracker
 import com.android.systemui.util.mockito.any
@@ -56,12 +57,12 @@
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -89,6 +90,7 @@
     @Mock private lateinit var applicationInfo: ApplicationInfo
     @Mock private lateinit var serviceInfo: ServiceInfo
     @Mock private lateinit var customTileStatePersister: CustomTileStatePersister
+    @Mock private lateinit var uiEventLogger: QsEventLogger
 
     private var displayTracker = FakeDisplayTracker(mContext)
     private lateinit var customTile: CustomTile
@@ -115,6 +117,7 @@
 
         customTileBuilder = CustomTile.Builder(
                 { tileHost },
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 36549fb..962b537 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -61,6 +61,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+import com.android.systemui.InstanceIdSequenceFake;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.ActivityStarter;
@@ -69,6 +70,8 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSEvent;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.QsEventLoggerFake;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.statusbar.StatusBarState;
 
@@ -106,7 +109,8 @@
     private ActivityStarter mActivityStarter;
 
     private UiEventLoggerFake mUiEventLoggerFake;
-    private InstanceId mInstanceId = InstanceId.fakeInstanceId(5);
+    private QsEventLoggerFake mQsEventLoggerFake;
+    private InstanceId mInstanceId;
 
     @Captor
     private ArgumentCaptor<LogMaker> mLogCaptor;
@@ -115,18 +119,29 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
         mTestableLooper = TestableLooper.get(this);
+
         mUiEventLoggerFake = new UiEventLoggerFake();
+        mQsEventLoggerFake =
+                new QsEventLoggerFake(mUiEventLoggerFake, new InstanceIdSequenceFake(10));
         when(mHost.indexOf(SPEC)).thenReturn(POSITION);
         when(mHost.getContext()).thenReturn(mContext);
-        when(mHost.getUiEventLogger()).thenReturn(mUiEventLoggerFake);
-        when(mHost.getNewInstanceId()).thenReturn(mInstanceId);
 
         Handler mainHandler = new Handler(mTestableLooper.getLooper());
 
-        mTile = new TileImpl(mHost, mTestableLooper.getLooper(), mainHandler, mFalsingManager,
-                mMetricsLogger, mStatusBarStateController, mActivityStarter, mQsLogger);
+        mTile = new TileImpl(
+                mHost,
+                mQsEventLoggerFake,
+                mTestableLooper.getLooper(),
+                mainHandler,
+                mFalsingManager,
+                mMetricsLogger,
+                mStatusBarStateController,
+                mActivityStarter,
+                mQsLogger
+        );
         mTile.initialize();
         mTestableLooper.processAllMessages();
+        mInstanceId = InstanceId.fakeInstanceId(mQsEventLoggerFake.getLastInstanceId());
 
         mTile.setTileSpec(SPEC);
     }
@@ -507,6 +522,7 @@
 
         protected TileImpl(
                 QSHost host,
+                QsEventLogger uiEventLogger,
                 Looper backgroundLooper,
                 Handler mainHandler,
                 FalsingManager falsingManager,
@@ -515,7 +531,7 @@
                 ActivityStarter activityStarter,
                 QSLogger qsLogger
         ) {
-            super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+            super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                     statusBarStateController, activityStarter, qsLogger);
             getState().state = Tile.STATE_ACTIVE;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
index 5e0190b..c60cecb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
@@ -22,8 +22,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
@@ -32,6 +30,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.settings.UserTracker
@@ -68,20 +67,21 @@
     private lateinit var mGlobalSettings: GlobalSettings
     @Mock
     private lateinit var mUserTracker: UserTracker
+    @Mock
+    private lateinit var mUiEventLogger: QsEventLogger
     private lateinit var mTestableLooper: TestableLooper
     private lateinit var mTile: AirplaneModeTile
 
-    private val mUiEventLogger: UiEventLogger = UiEventLoggerFake()
-
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         mTestableLooper = TestableLooper.get(this)
         Mockito.`when`(mHost.context).thenReturn(mContext)
-        Mockito.`when`(mHost.uiEventLogger).thenReturn(mUiEventLogger)
         Mockito.`when`(mHost.userContext).thenReturn(mContext)
 
-        mTile = AirplaneModeTile(mHost,
+        mTile = AirplaneModeTile(
+            mHost,
+            mUiEventLogger,
             mTestableLooper.looper,
             Handler(mTestableLooper.looper),
             FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
index f1e3e8a..52b8455 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
@@ -9,12 +9,12 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.NextAlarmController
@@ -28,8 +28,8 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -52,7 +52,7 @@
     @Mock
     private lateinit var nextAlarmController: NextAlarmController
     @Mock
-    private lateinit var uiEventLogger: UiEventLogger
+    private lateinit var uiEventLogger: QsEventLogger
     @Mock
     private lateinit var pendingIntent: PendingIntent
     @Captor
@@ -67,10 +67,10 @@
         testableLooper = TestableLooper.get(this)
 
         `when`(qsHost.context).thenReturn(mContext)
-        `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
 
         tile = AlarmTile(
             qsHost,
+            uiEventLogger,
             testableLooper.looper,
             Handler(testableLooper.looper),
             FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
index a5c0004..ff6814c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.BatteryController
@@ -43,10 +44,10 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -63,6 +64,8 @@
     @Mock
     private lateinit var qsHost: QSHost
     @Mock
+    private lateinit var uiEventLogger: QsEventLogger
+    @Mock
     private lateinit var metricsLogger: MetricsLogger
     @Mock
     private lateinit var statusBarStateController: StatusBarStateController
@@ -90,6 +93,7 @@
 
         tile = BatterySaverTile(
                 qsHost,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 FalsingManagerFake(),
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 2e77de2..5e7f68c 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
@@ -9,7 +9,6 @@
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.settingslib.Utils
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.systemui.R
@@ -20,6 +19,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.BluetoothController
@@ -49,8 +49,8 @@
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var bluetoothController: BluetoothController
+    @Mock private lateinit var uiEventLogger: QsEventLogger
 
-    private val uiEventLogger = UiEventLoggerFake()
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: FakeBluetoothTile
 
@@ -60,11 +60,11 @@
         testableLooper = TestableLooper.get(this)
 
         whenever(qsHost.context).thenReturn(mContext)
-        whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger)
 
         tile =
             FakeBluetoothTile(
                 qsHost,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 falsingManager,
@@ -211,6 +211,7 @@
 
     private class FakeBluetoothTile(
         qsHost: QSHost,
+        uiEventLogger: QsEventLogger,
         backgroundLooper: Looper,
         mainHandler: Handler,
         falsingManager: FalsingManager,
@@ -222,6 +223,7 @@
     ) :
         BluetoothTile(
             qsHost,
+            uiEventLogger,
             backgroundLooper,
             mainHandler,
             falsingManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
index 4193854..70d82fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
@@ -21,8 +21,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
@@ -30,6 +28,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLoggerFake
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
@@ -67,19 +66,21 @@
     private lateinit var privacyController: IndividualSensorPrivacyController
     @Mock
     private lateinit var keyguardStateController: KeyguardStateController
+    @Mock
+    private lateinit var uiEventLogger: QsEventLoggerFake
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: CameraToggleTile
-    private val uiEventLogger: UiEventLogger = UiEventLoggerFake()
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
         whenever(host.context).thenReturn(mContext)
-        whenever(host.uiEventLogger).thenReturn(uiEventLogger)
 
-        tile = CameraToggleTile(host,
+        tile = CameraToggleTile(
+                host,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 metricsLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index 64fd09d5..93ed994 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -43,6 +43,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.statusbar.connectivity.IconState;
 import com.android.systemui.statusbar.connectivity.NetworkController;
@@ -94,6 +95,8 @@
     private QSLogger mQSLogger;
     @Mock
     private DialogLaunchAnimator mDialogLaunchAnimator;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
     private CastTile mCastTile;
@@ -107,6 +110,7 @@
 
         mCastTile = new CastTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
index 13c30e9..2250ef3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
@@ -32,12 +32,12 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.FakeSettings;
@@ -67,7 +67,7 @@
     @Mock
     private QSLogger mQSLogger;
     @Mock
-    private UiEventLogger mUiEventLogger;
+    private QsEventLogger mUiEventLogger;
     @Mock
     private UserTracker mUserTracker;
 
@@ -83,10 +83,10 @@
         mTestableLooper = TestableLooper.get(this);
 
         when(mHost.getContext()).thenReturn(mContext);
-        when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger);
 
         mTile = new ColorCorrectionTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
index ff27e02..2e02bbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
@@ -32,7 +32,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
@@ -40,6 +39,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.UserTracker;
@@ -72,7 +72,7 @@
     @Mock
     private QSLogger mQSLogger;
     @Mock
-    private UiEventLogger mUiEventLogger;
+    private QsEventLogger mUiEventLogger;
     @Mock
     private UserTracker mUserTracker;
 
@@ -88,10 +88,10 @@
         mTestableLooper = TestableLooper.get(this);
 
         when(mHost.getContext()).thenReturn(mContext);
-        when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger);
 
         mTile = new ColorInversionTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
index b048643..176b33f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
@@ -21,7 +21,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
@@ -30,6 +29,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.DataSaverController
@@ -57,8 +57,8 @@
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var dataSaverController: DataSaverController
     @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+    @Mock private lateinit var uiEventLogger: QsEventLogger
 
-    private val uiEventLogger = UiEventLoggerFake()
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: DataSaverTile
 
@@ -68,11 +68,11 @@
         testableLooper = TestableLooper.get(this)
 
         Mockito.`when`(mHost.context).thenReturn(mContext)
-        Mockito.`when`(mHost.uiEventLogger).thenReturn(uiEventLogger)
 
         tile =
             DataSaverTile(
                 mHost,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 falsingManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
index b51c378..1346069 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -27,7 +27,6 @@
 import androidx.lifecycle.LifecycleOwner
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
@@ -44,6 +43,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.util.mockito.any
@@ -52,6 +52,7 @@
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -59,15 +60,14 @@
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.doNothing
 import org.mockito.Mockito.nullable
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
 import java.util.Optional
-import org.junit.After
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -95,7 +95,7 @@
     @Mock
     private lateinit var serviceInfo: ControlsServiceInfo
     @Mock
-    private lateinit var uiEventLogger: UiEventLogger
+    private lateinit var uiEventLogger: QsEventLogger
     @Captor
     private lateinit var listingCallbackCaptor:
             ArgumentCaptor<ControlsListingController.ControlsListingCallback>
@@ -118,7 +118,6 @@
         spiedContext = spy(mContext)
         doNothing().`when`(spiedContext).startActivity(any(Intent::class.java))
         `when`(qsHost.context).thenReturn(spiedContext)
-        `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
         `when`(controlsComponent.isEnabled()).thenReturn(true)
         `when`(controlsController.getPreferredSelection())
                 .thenReturn(SelectedItem.StructureItem(
@@ -399,6 +398,7 @@
     private fun createTile(): DeviceControlsTile {
         return DeviceControlsTile(
                 qsHost,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
index 6c0904e..f0e4e3a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
@@ -28,7 +28,6 @@
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
@@ -37,6 +36,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.ZenModeController
@@ -46,7 +46,6 @@
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
-import java.io.File
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -55,8 +54,9 @@
 import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import java.io.File
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -84,7 +84,7 @@
     private lateinit var qsLogger: QSLogger
 
     @Mock
-    private lateinit var uiEventLogger: UiEventLogger
+    private lateinit var uiEventLogger: QsEventLogger
 
     @Mock
     private lateinit var zenModeController: ZenModeController
@@ -109,7 +109,6 @@
         secureSettings = FakeSettings()
 
         whenever(qsHost.userId).thenReturn(DEFAULT_USER)
-        whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger)
 
         val wrappedContext = object : ContextWrapper(context) {
             override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences {
@@ -120,6 +119,7 @@
 
         tile = DndTile(
             qsHost,
+            uiEventLogger,
             testableLooper.looper,
             Handler(testableLooper.looper),
             FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
index 7d41aa6..f231c6e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
@@ -48,6 +48,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.UserTracker;
@@ -83,6 +84,8 @@
     private BroadcastDispatcher mBroadcastDispatcher;
     @Mock
     private UserTracker mUserTracker;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
 
@@ -258,6 +261,7 @@
             boolean dreamOnlyEnabledForSystemUser) {
         return new DreamTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
index 692a644..73aa699 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
@@ -6,7 +6,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
@@ -14,6 +13,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.FlashlightController
@@ -45,7 +45,8 @@
 
     @Mock private lateinit var flashlightController: FlashlightController
 
-    private val uiEventLogger = UiEventLoggerFake()
+    @Mock private lateinit var uiEventLogger: QsEventLogger
+
     private val falsingManager = FalsingManagerFake()
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: FlashlightTile
@@ -56,11 +57,11 @@
         testableLooper = TestableLooper.get(this)
 
         Mockito.`when`(qsHost.context).thenReturn(mockContext)
-        Mockito.`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
 
         tile =
             FlashlightTile(
                 qsHost,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 falsingManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
index eeebd4f..1d6f225 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
@@ -23,7 +23,6 @@
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.classifier.FalsingManagerFake
@@ -31,7 +30,8 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -52,13 +52,13 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 class FontScalingTileTest : SysuiTestCase() {
-    @Mock private lateinit var qsHost: QSTileHost
+    @Mock private lateinit var qsHost: QSHost
     @Mock private lateinit var metricsLogger: MetricsLogger
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var qsLogger: QSLogger
     @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
-    @Mock private lateinit var uiEventLogger: UiEventLogger
+    @Mock private lateinit var uiEventLogger: QsEventLogger
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var fontScalingTile: FontScalingTile
@@ -70,11 +70,11 @@
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
         `when`(qsHost.getContext()).thenReturn(mContext)
-        `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
 
         fontScalingTile =
             FontScalingTile(
                 qsHost,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
index 959e750..73f61d06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
@@ -38,6 +38,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.DataSaverController;
@@ -66,6 +67,8 @@
     private HotspotController mHotspotController;
     @Mock
     private DataSaverController mDataSaverController;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
     private HotspotTile mTile;
@@ -80,6 +83,7 @@
 
         mTile = new HotspotTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
index adfd7f7..7957c6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
@@ -35,6 +35,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
@@ -63,6 +64,8 @@
     private AccessPointController mAccessPointController;
     @Mock
     private InternetDialogFactory mInternetDialogFactory;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
     private InternetTile mTile;
@@ -76,6 +79,7 @@
 
         mTile = new InternetTile(
             mHost,
+            mUiEventLogger,
             mTestableLooper.getLooper(),
             new Handler(mTestableLooper.getLooper()),
             new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
index 3642e87..0bf0b38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
@@ -22,7 +22,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
@@ -30,6 +29,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
 import com.android.systemui.qs.tileimpl.QSTileImpl
@@ -43,8 +43,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -71,8 +71,9 @@
     private lateinit var keyguardStateController: KeyguardStateController
     @Mock
     private lateinit var panelInteractor: PanelInteractor
+    @Mock
+    private lateinit var uiEventLogger: QsEventLogger
 
-    private val uiEventLogger = UiEventLoggerFake()
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: LocationTile
 
@@ -80,10 +81,11 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
-        `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
         `when`(qsHost.context).thenReturn(mockContext)
 
-        tile = LocationTile(qsHost,
+        tile = LocationTile(
+            qsHost,
+            uiEventLogger,
             testableLooper.looper,
             Handler(testableLooper.looper),
             falsingManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
index e2f64b2..ceff546 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
@@ -21,8 +21,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
@@ -30,6 +28,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
@@ -67,19 +66,22 @@
     private lateinit var privacyController: IndividualSensorPrivacyController
     @Mock
     private lateinit var keyguardStateController: KeyguardStateController
+    @Mock
+    private lateinit var uiEventLogger: QsEventLogger
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: MicrophoneToggleTile
-    private val uiEventLogger: UiEventLogger = UiEventLoggerFake()
+
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
         whenever(host.context).thenReturn(mContext)
-        whenever(host.uiEventLogger).thenReturn(uiEventLogger)
 
-        tile = MicrophoneToggleTile(host,
+        tile = MicrophoneToggleTile(
+                host,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 metricsLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
index c7dae83..763a7e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 
 import org.junit.After;
@@ -70,6 +71,8 @@
     private QSLogger mQSLogger;
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
     private NfcTile mNfcTile;
@@ -84,6 +87,7 @@
 
         mNfcTile = new NfcTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
index 04af69c..6c8f76b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
@@ -23,8 +23,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
@@ -33,6 +31,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.LocationController
@@ -43,8 +42,8 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
 
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -68,17 +67,18 @@
 
     @Mock private lateinit var mNightDisplayListener: NightDisplayListener
 
+    @Mock private lateinit var mUiEventLogger: QsEventLogger
+
     private lateinit var mTestableLooper: TestableLooper
     private lateinit var mTile: NightDisplayTile
 
-    private val mUiEventLogger: UiEventLogger = UiEventLoggerFake()
+
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         mTestableLooper = TestableLooper.get(this)
         whenever(mHost.context).thenReturn(mContext)
-        whenever(mHost.uiEventLogger).thenReturn(mUiEventLogger)
         whenever(mHost.userContext).thenReturn(mContext)
         whenever(mNightDisplayListenerBuilder.setUser(anyInt()))
             .thenReturn(mNightDisplayListenerBuilder)
@@ -87,6 +87,7 @@
         mTile =
             NightDisplayTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.looper,
                 Handler(mTestableLooper.looper),
                 FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
index 652c138..c391153 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
@@ -33,6 +33,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.SecureSettings;
@@ -65,6 +66,8 @@
     private UserTracker mUserTracker;
     @Mock
     private SecureSettings mSecureSettings;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
     private OneHandedModeTile mTile;
@@ -78,6 +81,7 @@
 
         mTile = spy(new OneHandedModeTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
index 3125d45..6f2d904 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
@@ -30,8 +30,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
@@ -40,6 +38,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 
@@ -64,7 +63,8 @@
     private ActivityStarter mActivityStarter;
     @Mock
     private QSLogger mQSLogger;
-    private UiEventLogger mUiEventLogger = new UiEventLoggerFake();
+    @Mock
+    private QsEventLogger mUiEventLogger;
     @Mock
     private QRCodeScannerController mController;
 
@@ -79,6 +79,7 @@
 
         mTile = new QRCodeScannerTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index 596df78..b089e38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -58,8 +58,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
@@ -67,6 +65,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -109,7 +108,8 @@
     private ActivityStarter mActivityStarter;
     @Mock
     private QSLogger mQSLogger;
-    private UiEventLogger mUiEventLogger = new UiEventLoggerFake();
+    @Mock
+    private QsEventLogger mUiEventLogger;
     @Mock
     private QuickAccessWalletClient mQuickAccessWalletClient;
     @Mock
@@ -136,7 +136,6 @@
 
         doNothing().when(mSpiedContext).startActivity(any(Intent.class));
         when(mHost.getContext()).thenReturn(mSpiedContext);
-        when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger);
         when(mQuickAccessWalletClient.getServiceLabel()).thenReturn(LABEL);
         when(mQuickAccessWalletClient.getTileIcon()).thenReturn(mTileIcon);
         when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true);
@@ -146,6 +145,7 @@
 
         mTile = new QuickAccessWalletTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
index 7913628..d244594 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
@@ -39,6 +39,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.ReduceBrightColorsController;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -69,6 +70,8 @@
     private UserTracker mUserTracker;
     @Mock
     private ReduceBrightColorsController mReduceBrightColorsController;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
     private ReduceBrightColorsTile mTile;
@@ -85,6 +88,7 @@
                 true,
                 mReduceBrightColorsController,
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
index 5b94cfe..e106741 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
@@ -39,6 +39,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -87,6 +88,8 @@
     DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
     @Mock
     RotationPolicyWrapper mRotationPolicyWrapper;
+    @Mock
+    QsEventLogger mUiEventLogger;
 
     private RotationLockController mController;
     private TestableLooper mTestableLooper;
@@ -105,6 +108,7 @@
 
         mLockTile = new RotationLockTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index d9ed1a2..fff2b8f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -44,6 +44,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -86,6 +87,8 @@
     private DialogLaunchAnimator mDialogLaunchAnimator;
     @Mock
     private PanelInteractor mPanelInteractor;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
     private ScreenRecordTile mTile;
@@ -100,6 +103,7 @@
 
         mTile = new ScreenRecordTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
index b556571..79147e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
@@ -25,7 +25,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
@@ -33,6 +32,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.BatteryController
@@ -63,8 +63,8 @@
     @Mock private lateinit var configurationController: ConfigurationController
     @Mock private lateinit var batteryController: BatteryController
     @Mock private lateinit var locationController: LocationController
+    @Mock private lateinit var uiEventLogger: QsEventLogger
 
-    private val uiEventLogger = UiEventLoggerFake()
     private val falsingManager = FalsingManagerFake()
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: UiModeNightTile
@@ -81,11 +81,11 @@
         `when`(qsHost.userContext).thenReturn(mContext)
         `when`(mockContext.resources).thenReturn(resources)
         `when`(resources.configuration).thenReturn(configuration)
-        `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
 
         tile =
             UiModeNightTile(
                 qsHost,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 falsingManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 5ca3771..4bfd6a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -93,6 +93,7 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
@@ -665,7 +666,8 @@
                 mMetricsLogger,
                 mFeatureFlags,
                 mInteractionJankMonitor,
-                mShadeLog
+                mShadeLog,
+                mock(KeyguardFaceAuthInteractor.class)
         );
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
index d8ffe39..908f7cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
@@ -62,6 +62,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.plugins.FalsingManager;
@@ -239,7 +240,8 @@
                 mMetricsLogger,
                 mFeatureFlags,
                 mInteractionJankMonitor,
-                mShadeLogger
+                mShadeLogger,
+                mock(KeyguardFaceAuthInteractor.class)
         );
 
         mFragmentListener = mQsController.getQsFragmentListener();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 7d02219..9186c35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -100,7 +100,6 @@
         mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL);
 
         FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
-        fakeFeatureFlags.set(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE, true);
         fakeFeatureFlags.set(Flags.SENSITIVE_REVEAL_ANIM, false);
         mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
index 14e5f9e..2cc375b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
@@ -24,7 +24,6 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
@@ -68,4 +67,22 @@
 
         assertThat(shelfStatic).isTrue()
     }
+
+    @Test
+    fun shelfOnKeyguard_whenKeyguardShowing() = runTest {
+        val onKeyguard by collectLastValue(underTest.isShowingOnKeyguard)
+
+        keyguardRepository.setKeyguardShowing(true)
+
+        assertThat(onKeyguard).isTrue()
+    }
+
+    @Test
+    fun shelfNotOnKeyguard_whenKeyguardNotShowing() = runTest {
+        val onKeyguard by collectLastValue(underTest.isShowingOnKeyguard)
+
+        keyguardRepository.setKeyguardShowing(false)
+
+        assertThat(onKeyguard).isFalse()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
index 6c5fb8b..439edaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
@@ -69,4 +69,22 @@
 
         assertThat(canModifyNotifColor).isFalse()
     }
+
+    @Test
+    fun isClickable_whenKeyguardShowing() = runTest {
+        val isClickable by collectLastValue(underTest.isClickable)
+
+        keyguardRepository.setKeyguardShowing(true)
+
+        assertThat(isClickable).isTrue()
+    }
+
+    @Test
+    fun isNotClickable_whenKeyguardNotShowing() = runTest {
+        val isClickable by collectLastValue(underTest.isClickable)
+
+        keyguardRepository.setKeyguardShowing(false)
+
+        assertThat(isClickable).isFalse()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
index e6f10cd..5279740 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
@@ -23,6 +23,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.controls.pipeline.MediaDataManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -48,6 +49,7 @@
     @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
     @Mock
     private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
+    @Mock private lateinit var mediaDataManager: MediaDataManager
     @Mock private lateinit var stackLayout: NotificationStackScrollLayout
 
     private val testableResources = mContext.orCreateTestableResources
@@ -67,7 +69,9 @@
             NotificationStackSizeCalculator(
                 statusBarStateController = sysuiStatusBarStateController,
                 lockscreenShadeTransitionController = lockscreenShadeTransitionController,
-                testableResources.resources)
+                mediaDataManager = mediaDataManager,
+                testableResources.resources
+            )
     }
 
     @Test
@@ -76,7 +80,11 @@
 
         val maxNotifications =
             computeMaxKeyguardNotifications(
-                rows, spaceForNotifications = 0f, spaceForShelf = 0f, shelfHeight = 0f)
+                rows,
+                spaceForNotifications = 0f,
+                spaceForShelf = 0f,
+                shelfHeight = 0f
+            )
 
         assertThat(maxNotifications).isEqualTo(0)
     }
@@ -91,7 +99,8 @@
                 rows,
                 spaceForNotifications = Float.MAX_VALUE,
                 spaceForShelf = Float.MAX_VALUE,
-                shelfHeight)
+                shelfHeight
+            )
 
         assertThat(maxNotifications).isEqualTo(numberOfRows)
     }
@@ -111,6 +120,28 @@
     }
 
     @Test
+    fun computeMaxKeyguardNotifications_onLockscreenSpaceForMinHeightButNotIntrinsicHeight_returnsOne() {
+        setGapHeight(0f)
+        // No divider height since we're testing one element where index = 0
+
+        whenever(sysuiStatusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        whenever(lockscreenShadeTransitionController.fractionToShade).thenReturn(0f)
+
+        val row = createMockRow(10f, isSticky = true)
+        whenever(row.getMinHeight(any())).thenReturn(5)
+
+        val maxNotifications =
+            computeMaxKeyguardNotifications(
+                listOf(row),
+                /* spaceForNotifications= */ 5f,
+                /* spaceForShelf= */ 0f,
+                /* shelfHeight= */ 0f
+            )
+
+        assertThat(maxNotifications).isEqualTo(1)
+    }
+
+    @Test
     fun computeMaxKeyguardNotifications_spaceForTwo_returnsTwo() {
         setGapHeight(gapHeight)
         val shelfHeight = shelfHeight + dividerHeight
@@ -126,7 +157,11 @@
 
         val maxNotifications =
             computeMaxKeyguardNotifications(
-                rows, spaceForNotifications + 1, spaceForShelf, shelfHeight)
+                rows,
+                spaceForNotifications + 1,
+                spaceForShelf,
+                shelfHeight
+            )
 
         assertThat(maxNotifications).isEqualTo(2)
     }
@@ -136,24 +171,23 @@
         // Each row in separate section.
         setGapHeight(gapHeight)
 
-        val spaceForNotifications =
+        val notifSpace =
             listOf(
                     rowHeight,
                     dividerHeight + gapHeight + rowHeight,
                 )
                 .sum()
 
-        val spaceForShelf = dividerHeight + gapHeight + shelfHeight
-        val spaceUsed = spaceForNotifications + spaceForShelf
+        val shelfSpace = dividerHeight + gapHeight + shelfHeight
+        val spaceUsed = notifSpace + shelfSpace
         val rows =
             listOf(createMockRow(rowHeight), createMockRow(rowHeight), createMockRow(rowHeight))
 
         val maxNotifications =
-            computeMaxKeyguardNotifications(rows, spaceForNotifications, spaceForShelf, shelfHeight)
+            computeMaxKeyguardNotifications(rows, notifSpace, shelfSpace, shelfHeight)
         assertThat(maxNotifications).isEqualTo(2)
 
-        val height =
-            sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
+        val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
         assertThat(height).isEqualTo(spaceUsed)
     }
 
@@ -170,11 +204,14 @@
         // test that we only use space required
         val maxNotifications =
             computeMaxKeyguardNotifications(
-                rows, spaceForNotifications + 1, spaceForShelf, shelfHeight)
+                rows,
+                spaceForNotifications + 1,
+                spaceForShelf,
+                shelfHeight
+            )
         assertThat(maxNotifications).isEqualTo(1)
 
-        val height =
-            sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
+        val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
         assertThat(height).isEqualTo(spaceUsed)
     }
 
@@ -200,7 +237,65 @@
     }
 
     @Test
-    fun spaceNeeded_onLockscreen_usesMinHeight() {
+    fun getSpaceNeeded_onLockscreenEnoughSpaceStickyHun_intrinsicHeight() {
+        setGapHeight(0f)
+        // No divider height since we're testing one element where index = 0
+
+        val row = createMockRow(10f, isSticky = true)
+        whenever(row.getMinHeight(any())).thenReturn(5)
+
+        val space =
+            sizeCalculator.getSpaceNeeded(
+                row,
+                visibleIndex = 0,
+                previousView = null,
+                stack = stackLayout,
+                onLockscreen = true
+            )
+        assertThat(space.whenEnoughSpace).isEqualTo(10f)
+    }
+
+    @Test
+    fun getSpaceNeeded_onLockscreenEnoughSpaceNotStickyHun_minHeight() {
+        setGapHeight(0f)
+        // No divider height since we're testing one element where index = 0
+
+        val row = createMockRow(rowHeight)
+        whenever(row.heightWithoutLockscreenConstraints).thenReturn(10)
+        whenever(row.getMinHeight(any())).thenReturn(5)
+
+        val space =
+            sizeCalculator.getSpaceNeeded(
+                row,
+                visibleIndex = 0,
+                previousView = null,
+                stack = stackLayout,
+                onLockscreen = true
+            )
+        assertThat(space.whenEnoughSpace).isEqualTo(5)
+    }
+
+    @Test
+    fun getSpaceNeeded_onLockscreenSavingSpaceStickyHun_minHeight() {
+        setGapHeight(0f)
+        // No divider height since we're testing one element where index = 0
+
+        val expandableView = createMockRow(10f, isSticky = true)
+        whenever(expandableView.getMinHeight(any())).thenReturn(5)
+
+        val space =
+            sizeCalculator.getSpaceNeeded(
+                expandableView,
+                visibleIndex = 0,
+                previousView = null,
+                stack = stackLayout,
+                onLockscreen = true
+            )
+        assertThat(space.whenSavingSpace).isEqualTo(5)
+    }
+
+    @Test
+    fun getSpaceNeeded_onLockscreenSavingSpaceNotStickyHun_minHeight() {
         setGapHeight(0f)
         // No divider height since we're testing one element where index = 0
 
@@ -209,51 +304,34 @@
         whenever(expandableView.intrinsicHeight).thenReturn(10)
 
         val space =
-            sizeCalculator.spaceNeeded(
+            sizeCalculator.getSpaceNeeded(
                 expandableView,
                 visibleIndex = 0,
                 previousView = null,
                 stack = stackLayout,
-                onLockscreen = true)
-        assertThat(space).isEqualTo(5)
+                onLockscreen = true
+            )
+        assertThat(space.whenSavingSpace).isEqualTo(5)
     }
 
     @Test
-    fun spaceNeeded_fsiHunOnLockscreen_usesIntrinsicHeight() {
-        setGapHeight(0f)
-        // No divider height since we're testing one element where index = 0
-
-        val expandableView = createMockStickyRow(rowHeight)
-        whenever(expandableView.getMinHeight(any())).thenReturn(5)
-        whenever(expandableView.intrinsicHeight).thenReturn(10)
-
-        val space =
-                sizeCalculator.spaceNeeded(
-                        expandableView,
-                        visibleIndex = 0,
-                        previousView = null,
-                        stack = stackLayout,
-                        onLockscreen = true)
-        assertThat(space).isEqualTo(10)
-    }
-
-    @Test
-    fun spaceNeeded_notOnLockscreen_usesIntrinsicHeight() {
+    fun getSpaceNeeded_notOnLockscreen_intrinsicHeight() {
         setGapHeight(0f)
         // No divider height since we're testing one element where index = 0
 
         val expandableView = createMockRow(rowHeight)
-        whenever(expandableView.getMinHeight(any())).thenReturn(5)
-        whenever(expandableView.intrinsicHeight).thenReturn(10)
+        whenever(expandableView.getMinHeight(any())).thenReturn(1)
 
         val space =
-            sizeCalculator.spaceNeeded(
+            sizeCalculator.getSpaceNeeded(
                 expandableView,
                 visibleIndex = 0,
                 previousView = null,
                 stack = stackLayout,
-                onLockscreen = false)
-        assertThat(space).isEqualTo(10)
+                onLockscreen = false
+            )
+        assertThat(space.whenEnoughSpace).isEqualTo(rowHeight)
+        assertThat(space.whenSavingSpace).isEqualTo(rowHeight)
     }
 
     private fun computeMaxKeyguardNotifications(
@@ -264,7 +342,11 @@
     ): Int {
         setupChildren(rows)
         return sizeCalculator.computeMaxKeyguardNotifications(
-            stackLayout, spaceForNotifications, spaceForShelf, shelfHeight)
+            stackLayout,
+            spaceForNotifications,
+            spaceForShelf,
+            shelfHeight
+        )
     }
 
     private fun setupChildren(children: List<ExpandableView>) {
@@ -280,11 +362,13 @@
 
     private fun createMockRow(
         height: Float = rowHeight,
+        isSticky: Boolean = false,
         isRemoved: Boolean = false,
-        visibility: Int = VISIBLE
+        visibility: Int = VISIBLE,
     ): ExpandableNotificationRow {
         val row = mock(ExpandableNotificationRow::class.java)
         val entry = mock(NotificationEntry::class.java)
+        whenever(entry.isStickyAndNotDemoted).thenReturn(isSticky)
         val sbn = mock(StatusBarNotification::class.java)
         whenever(entry.sbn).thenReturn(sbn)
         whenever(row.entry).thenReturn(entry)
@@ -292,25 +376,7 @@
         whenever(row.visibility).thenReturn(visibility)
         whenever(row.getMinHeight(any())).thenReturn(height.toInt())
         whenever(row.intrinsicHeight).thenReturn(height.toInt())
-        return row
-    }
-
-    private fun createMockStickyRow(
-            height: Float = rowHeight,
-            isRemoved: Boolean = false,
-            visibility: Int = VISIBLE
-    ): ExpandableNotificationRow {
-        val row = mock(ExpandableNotificationRow::class.java)
-        val entry = mock(NotificationEntry::class.java)
-        whenever(entry.isStickyAndNotDemoted).thenReturn(true)
-
-        val sbn = mock(StatusBarNotification::class.java)
-        whenever(entry.sbn).thenReturn(sbn)
-        whenever(row.entry).thenReturn(entry)
-        whenever(row.isRemoved).thenReturn(isRemoved)
-        whenever(row.visibility).thenReturn(visibility)
-        whenever(row.getMinHeight(any())).thenReturn(height.toInt())
-        whenever(row.intrinsicHeight).thenReturn(height.toInt())
+        whenever(row.heightWithoutLockscreenConstraints).thenReturn(height.toInt())
         return row
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 746c92e..02c459b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -16,12 +16,10 @@
 
 package com.android.systemui.statusbar.phone
 
-import android.animation.Animator
 import android.os.Handler
 import android.os.PowerManager
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
-import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.SysuiTestCase
@@ -39,10 +37,8 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
-import org.mockito.Mockito
 import org.mockito.Mockito.anyLong
 import org.mockito.Mockito.never
-import org.mockito.Mockito.spy
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
@@ -111,27 +107,6 @@
         controller.onStartedWakingUp()
     }
 
-    @Test
-    fun testAnimClearsEndListener() {
-        val keyguardView = View(context)
-        val animator = spy(keyguardView.animate())
-        val keyguardSpy = spy(keyguardView)
-        Mockito.`when`(keyguardSpy.animate()).thenReturn(animator)
-        val listener = ArgumentCaptor.forClass(Animator.AnimatorListener::class.java)
-        val endAction = ArgumentCaptor.forClass(Runnable::class.java)
-        controller.animateInKeyguard(keyguardSpy, Runnable {})
-        Mockito.verify(animator).setListener(listener.capture())
-        Mockito.verify(animator).withEndAction(endAction.capture())
-
-        // Verify that the listener is cleared if we cancel it.
-        listener.value.onAnimationCancel(null)
-        Mockito.verify(animator).setListener(null)
-
-        // Verify that the listener is also cleared if the end action is triggered.
-        endAction.value.run()
-        verify(animator, times(2)).setListener(null)
-    }
-
     /**
      * The AOD UI is shown during the screen off animation, after a delay to allow the light reveal
      * animation to start. If the device is woken up during the screen off, we should *never* do
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
index ddb7f4d..01bec87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -32,9 +32,8 @@
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
@@ -69,14 +68,8 @@
                 FakeConnectivityRepository(),
             )
 
-        val subscriptionIdsFlow =
-            interactor.filteredSubscriptions
-                .map { subs -> subs.map { it.subscriptionId } }
-                .stateIn(testScope.backgroundScope, SharingStarted.WhileSubscribed(), listOf())
-
         underTest =
             MobileIconsViewModel(
-                subscriptionIdsFlow,
                 logger,
                 verboseLogger,
                 interactor,
@@ -90,6 +83,32 @@
     }
 
     @Test
+    fun subscriptionIdsFlow_matchesInteractor() =
+        testScope.runTest {
+            var latest: List<Int>? = null
+            val job = underTest.subscriptionIdsFlow.onEach { latest = it }.launchIn(this)
+
+            interactor.filteredSubscriptions.value =
+                listOf(
+                    SubscriptionModel(subscriptionId = 1, isOpportunistic = false),
+                )
+            assertThat(latest).isEqualTo(listOf(1))
+
+            interactor.filteredSubscriptions.value =
+                listOf(
+                    SubscriptionModel(subscriptionId = 2, isOpportunistic = false),
+                    SubscriptionModel(subscriptionId = 5, isOpportunistic = true),
+                    SubscriptionModel(subscriptionId = 7, isOpportunistic = true),
+                )
+            assertThat(latest).isEqualTo(listOf(2, 5, 7))
+
+            interactor.filteredSubscriptions.value = emptyList()
+            assertThat(latest).isEmpty()
+
+            job.cancel()
+        }
+
+    @Test
     fun `caching - mobile icon view model is reused for same sub id`() =
         testScope.runTest {
             val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
new file mode 100644
index 0000000..9cf3e44
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import static android.media.AudioManager.CSD_WARNING_DOSE_REACHED_1X;
+import static android.media.AudioManager.CSD_WARNING_DOSE_REPEATED_5X;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.media.AudioManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class CsdWarningDialogTest extends SysuiTestCase {
+
+    private NotificationManager mNotificationManager;
+    private AudioManager mAudioManager;
+
+    @Before
+    public void setup() {
+        mNotificationManager = mock(NotificationManager.class);
+        getContext().addMockSystemService(NotificationManager.class, mNotificationManager);
+
+        mAudioManager = mock(AudioManager.class);
+        getContext().addMockSystemService(AudioManager.class, mAudioManager);
+    }
+
+    @Test
+    public void create1XCsdDialogAndWait_sendsNotification() {
+        FakeExecutor executor =  new FakeExecutor(new FakeSystemClock());
+        // instantiate directly instead of via factory; we don't want executor to be @Background
+        CsdWarningDialog dialog = new CsdWarningDialog(CSD_WARNING_DOSE_REACHED_1X, mContext,
+                mAudioManager, mNotificationManager, executor, null);
+
+        dialog.show();
+        executor.advanceClockToLast();
+        executor.runAllReady();
+        dialog.dismiss();
+
+        verify(mNotificationManager).notify(
+                eq(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO), any(Notification.class));
+    }
+
+    @Test
+    public void create5XCsdDiSalogAndWait_willNotSendNotification() {
+        FakeExecutor executor =  new FakeExecutor(new FakeSystemClock());
+        CsdWarningDialog dialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext,
+                mAudioManager, mNotificationManager, executor, null);
+
+        dialog.show();
+        executor.advanceClockToLast();
+        executor.runAllReady();
+        dialog.dismiss();
+
+        verify(mNotificationManager, never()).notify(
+                eq(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO), any(Notification.class));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index d419095..eb26888 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -102,6 +102,15 @@
     InteractionJankMonitor mInteractionJankMonitor;
     @Mock
     private DumpManager mDumpManager;
+    @Mock CsdWarningDialog mCsdWarningDialog;
+
+    private final CsdWarningDialog.Factory mCsdWarningDialogFactory =
+            new CsdWarningDialog.Factory() {
+        @Override
+        public CsdWarningDialog create(int warningType, Runnable onCleanup) {
+            return mCsdWarningDialog;
+        }
+    };
 
     @Before
     public void setup() throws Exception {
@@ -124,6 +133,7 @@
                 mInteractionJankMonitor,
                 mDeviceConfigProxy,
                 mExecutor,
+                mCsdWarningDialogFactory,
                 mDumpManager
             );
         mDialog.init(0, null);
@@ -352,6 +362,14 @@
         mDialog.getDialogView().animate().cancel();
     }
 
+    @Test
+    public void showCsdWarning_dialogShown() {
+        mDialog.showCsdWarningH(AudioManager.CSD_WARNING_DOSE_REACHED_1X,
+                CsdWarningDialog.NO_ACTION_TIMEOUT_MS);
+
+        verify(mCsdWarningDialog).show();
+    }
+
 /*
     @Test
     public void testContentDescriptions() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
index c08ecd0..738f09d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -24,7 +24,6 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.map
 
 class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
 
@@ -46,14 +45,19 @@
     private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null)
     val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> =
         _runningAuthRequest.asStateFlow()
-    override val isAuthRunning = _runningAuthRequest.map { it != null }
+
+    private val _isAuthRunning = MutableStateFlow(false)
+    override val isAuthRunning: StateFlow<Boolean> = _isAuthRunning
+
     override val isBypassEnabled = MutableStateFlow(false)
 
     override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
         _runningAuthRequest.value = uiEvent to fallbackToDetection
+        _isAuthRunning.value = true
     }
 
     override fun cancel() {
+        _isAuthRunning.value = false
         _runningAuthRequest.value = null
     }
 }
diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk
index cbca3f0..a41d0e5 100644
--- a/packages/overlays/Android.mk
+++ b/packages/overlays/Android.mk
@@ -32,6 +32,7 @@
 	NavigationBarModeGesturalOverlayWideBack \
 	NavigationBarModeGesturalOverlayExtraWideBack \
 	TransparentNavigationBarOverlay \
+	NotesRoleEnabledOverlay \
 	preinstalled-packages-platform-overlays.xml
 
 include $(BUILD_PHONY_PACKAGE)
diff --git a/core/tests/expresslog/Android.bp b/packages/overlays/NotesRoleEnabledOverlay/Android.bp
similarity index 64%
rename from core/tests/expresslog/Android.bp
rename to packages/overlays/NotesRoleEnabledOverlay/Android.bp
index cab49a7..68ebd96 100644
--- a/core/tests/expresslog/Android.bp
+++ b/packages/overlays/NotesRoleEnabledOverlay/Android.bp
@@ -1,16 +1,18 @@
-// Copyright (C) 2023 The Android Open Source Project
+//
+//  Copyright 2023, The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
 //
-//      http://www.apache.org/licenses/LICENSE-2.0
+//     http://www.apache.org/licenses/LICENSE-2.0
 //
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+//
 
 package {
     // See: http://go/android-license-faq
@@ -21,27 +23,8 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-android_test {
-    name: "ExpressLogTests",
-
-    srcs: [
-        "src/**/*.java",
-    ],
-
-    static_libs: [
-        "androidx.test.rules",
-        "modules-utils-build",
-    ],
-
-    libs: [
-        "android.test.base",
-        "android.test.runner",
-    ],
-
-    platform_apis: true,
-    test_suites: [
-        "general-tests",
-    ],
-
-    certificate: "platform",
+runtime_resource_overlay {
+    name: "NotesRoleEnabledOverlay",
+    theme: "NotesRoleEnabled",
+    product_specific: true,
 }
diff --git a/packages/overlays/NotesRoleEnabledOverlay/AndroidManifest.xml b/packages/overlays/NotesRoleEnabledOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..c01178d
--- /dev/null
+++ b/packages/overlays/NotesRoleEnabledOverlay/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<!--
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.role.notes.enabled"
+        android:versionCode="1"
+        android:versionName="1.0">
+    <overlay android:targetPackage="android"
+        android:priority="1"/>
+
+    <application android:label="@string/notes_role_enabled_overlay_title" android:hasCode="false"/>
+</manifest>
\ No newline at end of file
diff --git a/packages/overlays/NotesRoleEnabledOverlay/res/values/config.xml b/packages/overlays/NotesRoleEnabledOverlay/res/values/config.xml
new file mode 100644
index 0000000..f27f5f4
--- /dev/null
+++ b/packages/overlays/NotesRoleEnabledOverlay/res/values/config.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+    <!-- Whether the default notes role should be enabled. In builds without 
+    device-specific overlays, this can be controlled in developer options. -->
+     <bool name="config_enableDefaultNotes">true</bool>
+
+    <!-- Whether the default notes role for work profile should be enabled. 
+    In builds without device-specific overlays, this can be controlled in 
+    developer options. -->
+    <bool name="config_enableDefaultNotesForWorkProfile">true</bool>
+</resources>
diff --git a/packages/overlays/NotesRoleEnabledOverlay/res/values/strings.xml b/packages/overlays/NotesRoleEnabledOverlay/res/values/strings.xml
new file mode 100644
index 0000000..3edbb57
--- /dev/null
+++ b/packages/overlays/NotesRoleEnabledOverlay/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Name of overlay [CHAR LIMIT=64] -->
+    <string name="notes_role_enabled_overlay_title" translatable="false">Notes Role enabled</string>
+</resources>
\ No newline at end of file
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 4702734..21d0979 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -402,5 +402,7 @@
     // Package: android
     NOTE_ALL_MANAGED_SUBSCRIPTIONS_AND_MANAGED_PROFILE_OFF = 1006;
 
+    // Notify the user that audio was lowered based on Calculated Sound Dose (CSD)
+    NOTE_CSD_LOWER_AUDIO = 1007;
   }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 51325e7..0bdb0c8 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -112,6 +112,7 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.view.Display;
 import android.view.IWindow;
 import android.view.InputDevice;
@@ -160,6 +161,7 @@
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.utils.Slogf;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 import com.android.settingslib.RestrictedLockUtils;
@@ -301,7 +303,23 @@
     private final List<SendWindowStateChangedEventRunnable> mSendWindowStateChangedEventRunnables =
             new ArrayList<>();
 
-    private int mCurrentUserId = UserHandle.USER_SYSTEM;
+    @GuardedBy("mLock")
+    private @UserIdInt int mCurrentUserId = UserHandle.USER_SYSTEM;
+
+    // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation:
+    // when the UiAutomation is set in a visible background user, mCurrentUserId points to that user
+    // and mRealCurrentUserId points to the "real" current user; otherwise, mRealCurrentUserId
+    // is set as UserHandle.USER_CURRENT.
+    @GuardedBy("mLock")
+    private @UserIdInt int mRealCurrentUserId = UserHandle.USER_CURRENT;
+
+    // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation
+    // purposes - in the long term, the whole service should be refactored so it handles "visible"
+    // users, not current user. Notice that because this is temporary, it's not trying to optimize
+    // performance / utilization (for example, it's not using an IntArray)
+    @GuardedBy("mLock")
+    @Nullable // only set when device supports visible background users
+    private final SparseBooleanArray mVisibleBgUserIds;
 
     //TODO: Remove this hack
     private boolean mInitialized;
@@ -316,6 +334,7 @@
     private SparseArray<SurfaceControl> mA11yOverlayLayers = new SparseArray<>();
 
     private final FlashNotificationsController mFlashNotificationsController;
+    private final UserManagerInternal mUmi;
 
     private AccessibilityUserState getCurrentUserStateLocked() {
         return getUserStateLocked(mCurrentUserId);
@@ -445,6 +464,10 @@
             mHasInputFilter = true;
         }
         mFlashNotificationsController = new FlashNotificationsController(mContext);
+        mUmi = LocalServices.getService(UserManagerInternal.class);
+        // TODO(b/255426725): not used on tests
+        mVisibleBgUserIds = null;
+
         init();
     }
 
@@ -477,6 +500,15 @@
         mProxyManager = new ProxyManager(mLock, mA11yWindowManager, mContext, mMainHandler,
                 mUiAutomationManager, this);
         mFlashNotificationsController = new FlashNotificationsController(mContext);
+        mUmi = LocalServices.getService(UserManagerInternal.class);
+
+        if (UserManager.isVisibleBackgroundUsersEnabled()) {
+            mVisibleBgUserIds = new SparseBooleanArray();
+            mUmi.addUserVisibilityListener((u, v) -> onUserVisibilityChanged(u, v));
+        } else {
+            mVisibleBgUserIds = null;
+        }
+
         init();
     }
 
@@ -493,6 +525,12 @@
         return mCurrentUserId;
     }
 
+    @GuardedBy("mLock")
+    @Override
+    public SparseBooleanArray getVisibleUserIdsLocked() {
+        return mVisibleBgUserIds;
+    }
+
     @Override
     public boolean isAccessibilityButtonShown() {
         return mIsAccessibilityButtonShown;
@@ -1362,6 +1400,7 @@
     public void registerUiTestAutomationService(IBinder owner,
             IAccessibilityServiceClient serviceClient,
             AccessibilityServiceInfo accessibilityServiceInfo,
+            int userId,
             int flags) {
         if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".registerUiTestAutomationService",
@@ -1374,6 +1413,7 @@
                 FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE);
 
         synchronized (mLock) {
+            changeCurrentUserForTestAutomationIfNeededLocked(userId);
             mUiAutomationManager.registerUiTestAutomationServiceLocked(owner, serviceClient,
                     mContext, accessibilityServiceInfo, sIdCounter++, mMainHandler,
                     mSecurityPolicy, this, getTraceManager(), mWindowManagerService,
@@ -1390,9 +1430,49 @@
         }
         synchronized (mLock) {
             mUiAutomationManager.unregisterUiTestAutomationServiceLocked(serviceClient);
+            restoreCurrentUserAfterTestAutomationIfNeededLocked();
         }
     }
 
+    // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation
+    @GuardedBy("mLock")
+    private void changeCurrentUserForTestAutomationIfNeededLocked(@UserIdInt int userId) {
+        if (mVisibleBgUserIds == null) {
+            Slogf.d(LOG_TAG, "changeCurrentUserForTestAutomationIfNeededLocked(%d): ignoring "
+                    + "because device doesn't support visible background users", userId);
+            return;
+        }
+        if (!mVisibleBgUserIds.get(userId)) {
+            Slogf.wtf(LOG_TAG, "Cannot change current user to %d as it's not visible "
+                    + "(mVisibleUsers=%s)", userId, mVisibleBgUserIds);
+            return;
+        }
+        if (mCurrentUserId == userId) {
+            Slogf.w(LOG_TAG, "NOT changing current user for test automation purposes as it is "
+                    + "already %d", mCurrentUserId);
+            return;
+        }
+        Slogf.i(LOG_TAG, "Changing current user from %d to %d for test automation purposes",
+                mCurrentUserId, userId);
+        mRealCurrentUserId = mCurrentUserId;
+        switchUser(userId);
+    }
+
+    // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation
+    @GuardedBy("mLock")
+    private void restoreCurrentUserAfterTestAutomationIfNeededLocked() {
+        if (mVisibleBgUserIds == null) {
+            Slogf.d(LOG_TAG, "restoreCurrentUserForTestAutomationIfNeededLocked(): ignoring "
+                    + "because device doesn't support visible background users");
+            return;
+        }
+        Slogf.i(LOG_TAG, "Restoring current user to %d after using %d for test automation purposes",
+                mRealCurrentUserId, mCurrentUserId);
+        int currentUserId = mRealCurrentUserId;
+        mRealCurrentUserId = UserHandle.USER_CURRENT;
+        switchUser(currentUserId);
+    }
+
     @Override
     public IBinder getWindowToken(int windowId, int userId) {
         if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
@@ -2291,8 +2371,7 @@
     private void updateServicesLocked(AccessibilityUserState userState) {
         Map<ComponentName, AccessibilityServiceConnection> componentNameToServiceMap =
                 userState.mComponentNameToServiceMap;
-        boolean isUnlockingOrUnlocked = LocalServices.getService(UserManagerInternal.class)
-                    .isUserUnlockingOrUnlocked(userState.mUserId);
+        boolean isUnlockingOrUnlocked = mUmi.isUserUnlockingOrUnlocked(userState.mUserId);
 
         for (int i = 0, count = userState.mInstalledServices.size(); i < count; i++) {
             AccessibilityServiceInfo installedService = userState.mInstalledServices.get(i);
@@ -2593,6 +2672,19 @@
         }
     }
 
+    private void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) {
+        if (DEBUG) {
+            Slogf.d(LOG_TAG, "onUserVisibilityChanged(): %d => %b", userId, visible);
+        }
+        synchronized (mLock) {
+            if (visible) {
+                mVisibleBgUserIds.put(userId, visible);
+            } else {
+                mVisibleBgUserIds.delete(userId);
+            }
+        }
+    }
+
     /**
      * Called when any property of the user state has changed.
      *
@@ -4025,7 +4117,16 @@
             pw.println("ACCESSIBILITY MANAGER (dumpsys accessibility)");
             pw.println();
             pw.append("currentUserId=").append(String.valueOf(mCurrentUserId));
+            if (mRealCurrentUserId != UserHandle.USER_CURRENT
+                    && mCurrentUserId != mRealCurrentUserId) {
+                pw.append(" (set for UiAutomation purposes; \"real\" current user is ")
+                        .append(String.valueOf(mRealCurrentUserId)).append(")");
+            }
             pw.println();
+            if (mVisibleBgUserIds != null) {
+                pw.append("visibleBgUserIds=").append(mVisibleBgUserIds.toString());
+                pw.println();
+            }
             pw.append("hasWindowMagnificationConnection=").append(
                     String.valueOf(getWindowMagnificationMgr().isConnected()));
             pw.println();
@@ -4052,6 +4153,7 @@
             }
             pw.println();
             mProxyManager.dump(fd, pw, args);
+            mA11yDisplayListener.dump(fd, pw, args);
         }
     }
 
@@ -4437,6 +4539,20 @@
             /* do nothing */
         }
 
+        void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            pw.println("Accessibility Display Listener:");
+            pw.println("    SystemUI uid: " + mSystemUiUid);
+            int size = mDisplaysList.size();
+            pw.printf("    %d valid display%s: ", size, (size == 1 ? "" : "s"));
+            for (int i = 0; i < size; i++) {
+                pw.print(mDisplaysList.get(i).getDisplayId());
+                if (i < size - 1) {
+                    pw.print(", ");
+                }
+            }
+            pw.println();
+        }
+
         private boolean isValidDisplay(@Nullable Display display) {
             if (display == null || display.getType() == Display.TYPE_OVERLAY) {
                 return false;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index c37ea50..8865623 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -39,6 +39,7 @@
 import android.os.UserManager;
 import android.util.ArraySet;
 import android.util.Slog;
+import android.util.SparseBooleanArray;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.inputmethod.InputMethodInfo;
 
@@ -88,6 +89,12 @@
          */
         int getCurrentUserIdLocked();
         // TODO: Should include resolveProfileParentLocked, but that was already in SecurityPolicy
+
+        // TODO(b/255426725): temporary hack; see comment on A11YMS.mVisibleBgUserIds
+        /**
+         * Returns the {@link android.os.UserManager#getVisibleUsers() visible users}.
+         */
+        @Nullable SparseBooleanArray getVisibleUserIdsLocked();
     }
 
     private final Context mContext;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index baed181..a8a5365 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -51,6 +51,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.accessibility.AccessibilitySecurityPolicy.AccessibilityUserManager;
+import com.android.server.utils.Slogf;
 import com.android.server.wm.WindowManagerInternal;
 
 import java.io.FileDescriptor;
@@ -59,6 +60,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * This class provides APIs for accessibility manager to manage {@link AccessibilityWindowInfo}s and
@@ -67,6 +69,7 @@
 public class AccessibilityWindowManager {
     private static final String LOG_TAG = "AccessibilityWindowManager";
     private static final boolean DEBUG = false;
+    private static final boolean VERBOSE = false;
 
     private static int sNextWindowId;
 
@@ -209,6 +212,9 @@
          * Constructor for DisplayWindowsObserver.
          */
         DisplayWindowsObserver(int displayId) {
+            if (DEBUG) {
+                Slogf.d(LOG_TAG, "Creating DisplayWindowsObserver for displayId %d", displayId);
+            }
             mDisplayId = displayId;
         }
 
@@ -430,12 +436,27 @@
             synchronized (mLock) {
                 updateWindowsByWindowAttributesLocked(windows);
                 if (DEBUG) {
-                    Slog.i(LOG_TAG, "Display Id = " + mDisplayId);
-                    Slog.i(LOG_TAG, "Windows changed: " + windows);
+                    Slogf.i(LOG_TAG, "mDisplayId=%d, topFocusedDisplayId=%d, currentUserId=%d, "
+                            + "visibleBgUsers=%s", mDisplayId, topFocusedDisplayId,
+                            mAccessibilityUserManager.getCurrentUserIdLocked(),
+                            mAccessibilityUserManager.getVisibleUserIdsLocked());
+                    if (VERBOSE) {
+                        Slogf.i(LOG_TAG, "%d windows changed: %s ", windows.size(), windows);
+                    } else {
+                        List<String> windowsInfo = windows.stream()
+                                .map(w -> "{displayId=" + w.displayId + ", title=" + w.title + "}")
+                                .collect(Collectors.toList());
+                        Slogf.i(LOG_TAG, "%d windows changed: %s", windows.size(), windowsInfo);
+                    }
                 }
                 if (shouldUpdateWindowsLocked(forceSend, windows)) {
                     mTopFocusedDisplayId = topFocusedDisplayId;
                     mTopFocusedWindowToken = topFocusedWindowToken;
+                    if (DEBUG) {
+                        Slogf.d(LOG_TAG, "onWindowsForAccessibilityChanged(): updating windows for "
+                                + "display %d and token %s",
+                                topFocusedDisplayId, topFocusedWindowToken);
+                    }
                     cacheWindows(windows);
                     // Lets the policy update the focused and active windows.
                     updateWindowsLocked(mAccessibilityUserManager.getCurrentUserIdLocked(),
@@ -443,6 +464,11 @@
                     // Someone may be waiting for the windows - advertise it.
                     mLock.notifyAll();
                 }
+                else if (DEBUG) {
+                    Slogf.d(LOG_TAG, "onWindowsForAccessibilityChanged(): NOT updating windows for "
+                            + "display %d and token %s",
+                            topFocusedDisplayId, topFocusedWindowToken);
+                }
             }
         }
 
@@ -472,6 +498,12 @@
             }
 
             final int windowCount = windows.size();
+            if (VERBOSE) {
+                Slogf.v(LOG_TAG,
+                        "shouldUpdateWindowsLocked(): mDisplayId=%d, windowCount=%d, "
+                        + "mCachedWindowInfos.size()=%d, windows.size()=%d", mDisplayId,
+                        windowCount, mCachedWindowInfos.size(), windows.size());
+            }
             // We computed the windows and if they changed notify the client.
             if (mCachedWindowInfos.size() != windowCount) {
                 // Different size means something changed.
@@ -1274,7 +1306,7 @@
      */
     @Nullable
     public RemoteAccessibilityConnection getConnectionLocked(int userId, int windowId) {
-        if (DEBUG) {
+        if (VERBOSE) {
             Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId);
         }
         RemoteAccessibilityConnection connection = mGlobalInteractionConnections.get(windowId);
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 3ecf933..8fc30e4 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -80,7 +80,7 @@
 import android.util.apk.ApkSigningBlockUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.expresslog.Histogram;
+import com.android.modules.expresslog.Histogram;
 import com.android.internal.os.IBinaryTransparencyService;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.pm.ApexManager;
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 92889c0..d256aea 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -426,7 +426,7 @@
                                 }
                                 int impact = registeredObserver.onHealthCheckFailed(
                                         versionedPackage, failureReason, mitigationCount);
-                                if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
+                                if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
                                         && impact < currentObserverImpact) {
                                     currentObserverToNotify = registeredObserver;
                                     currentObserverImpact = impact;
@@ -466,7 +466,7 @@
             if (registeredObserver != null) {
                 int impact = registeredObserver.onHealthCheckFailed(
                         failingPackage, failureReason, 1);
-                if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
+                if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
                         && impact < currentObserverImpact) {
                     currentObserverToNotify = registeredObserver;
                     currentObserverImpact = impact;
@@ -494,7 +494,7 @@
                     PackageHealthObserver registeredObserver = observer.registeredObserver;
                     if (registeredObserver != null) {
                         int impact = registeredObserver.onBootLoop(mitigationCount);
-                        if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
+                        if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
                                 && impact < currentObserverImpact) {
                             currentObserverToNotify = registeredObserver;
                             currentObserverImpact = impact;
@@ -576,19 +576,23 @@
 
     /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
     @Retention(SOURCE)
-    @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_NONE,
-                     PackageHealthObserverImpact.USER_IMPACT_LOW,
-                     PackageHealthObserverImpact.USER_IMPACT_MEDIUM,
-                     PackageHealthObserverImpact.USER_IMPACT_HIGH})
+    @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_10,
+                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
+                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_50,
+                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_100})
     public @interface PackageHealthObserverImpact {
         /** No action to take. */
-        int USER_IMPACT_NONE = 0;
+        int USER_IMPACT_LEVEL_0 = 0;
         /* Action has low user impact, user of a device will barely notice. */
-        int USER_IMPACT_LOW = 1;
-        /* Action has medium user impact, user of a device will likely notice. */
-        int USER_IMPACT_MEDIUM = 3;
+        int USER_IMPACT_LEVEL_10 = 10;
+        /* Actions having medium user impact, user of a device will likely notice. */
+        int USER_IMPACT_LEVEL_30 = 30;
+        int USER_IMPACT_LEVEL_50 = 50;
+        int USER_IMPACT_LEVEL_70 = 70;
         /* Action has high user impact, a last resort, user of a device will be very frustrated. */
-        int USER_IMPACT_HIGH = 5;
+        int USER_IMPACT_LEVEL_100 = 100;
     }
 
     /** Register instances of this interface to receive notifications on package failure. */
@@ -633,7 +637,7 @@
          *                        boot loop (including this time).
          */
         default @PackageHealthObserverImpact int onBootLoop(int mitigationCount) {
-            return PackageHealthObserverImpact.USER_IMPACT_NONE;
+            return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
         }
 
         /**
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index 3de65f9..6e2e5f7 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -107,7 +107,7 @@
     static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG =
             "namespace_to_package_mapping";
     @VisibleForTesting
-    static final long FACTORY_RESET_THROTTLE_DURATION_MS = TimeUnit.MINUTES.toMillis(10);
+    static final long DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN = 10;
 
     private static final String NAME = "rescue-party-observer";
 
@@ -117,6 +117,8 @@
             "persist.device_config.configuration.disable_rescue_party";
     private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
             "persist.device_config.configuration.disable_rescue_party_factory_reset";
+    private static final String PROP_THROTTLE_DURATION_MIN_FLAG =
+            "persist.device_config.configuration.rescue_party_throttle_duration_min";
 
     private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
             | ApplicationInfo.FLAG_SYSTEM;
@@ -489,13 +491,14 @@
         switch(rescueLevel) {
             case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
             case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
-                return PackageHealthObserverImpact.USER_IMPACT_LOW;
+                return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10;
             case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
             case LEVEL_WARM_REBOOT:
+                return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50;
             case LEVEL_FACTORY_RESET:
-                return PackageHealthObserverImpact.USER_IMPACT_HIGH;
+                return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100;
             default:
-                return PackageHealthObserverImpact.USER_IMPACT_NONE;
+                return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
         }
     }
 
@@ -633,7 +636,7 @@
                 return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
                         mayPerformReboot(failedPackage)));
             } else {
-                return PackageHealthObserverImpact.USER_IMPACT_NONE;
+                return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
             }
         }
 
@@ -677,7 +680,7 @@
         @Override
         public int onBootLoop(int mitigationCount) {
             if (isDisabled()) {
-                return PackageHealthObserverImpact.USER_IMPACT_NONE;
+                return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
             }
             return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true));
         }
@@ -721,7 +724,9 @@
         private boolean shouldThrottleReboot() {
             Long lastResetTime = SystemProperties.getLong(PROP_LAST_FACTORY_RESET_TIME_MS, 0);
             long now = System.currentTimeMillis();
-            return now < lastResetTime + FACTORY_RESET_THROTTLE_DURATION_MS;
+            long throttleDurationMin = SystemProperties.getLong(PROP_THROTTLE_DURATION_MIN_FLAG,
+                    DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN);
+            return now < lastResetTime + TimeUnit.MINUTES.toMillis(throttleDurationMin);
         }
 
         private boolean isPersistentSystemApp(@NonNull String packageName) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index ae5dbe1..44e198b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -213,7 +213,7 @@
     private static final boolean DEFAULT_USE_TIERED_CACHED_ADJ = false;
     private static final long DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME = 60 * 1000;
 
-    private static final boolean DEFAULT_USE_MODERN_TRIM = false;
+    private static final boolean DEFAULT_USE_MODERN_TRIM = true;
 
     /**
      * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 9e61ce4..0767218 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -20,6 +20,7 @@
 import static com.android.server.am.BroadcastRecord.deliveryStateToString;
 import static com.android.server.am.BroadcastRecord.isReceiverEquals;
 
+import android.annotation.CheckResult;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -187,6 +188,12 @@
     private @Reason int mRunnableAtReason = REASON_EMPTY;
     private boolean mRunnableAtInvalidated;
 
+    /**
+     * Last state applied by {@link #updateDeferredStates}, used to quickly
+     * determine if a state transition is occurring.
+     */
+    private boolean mLastDeferredStates;
+
     private boolean mUidCached;
     private boolean mProcessInstrumented;
     private boolean mProcessPersistent;
@@ -235,7 +242,15 @@
      */
     @Nullable
     public BroadcastRecord enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record,
-            int recordIndex, boolean wouldBeSkipped) {
+            int recordIndex, boolean wouldBeSkipped,
+            @NonNull BroadcastConsumer deferredStatesApplyConsumer) {
+        // When updateDeferredStates() has already applied a deferred state to
+        // all pending items, apply to this new broadcast too
+        if (mLastDeferredStates && record.deferUntilActive
+                && (record.getDeliveryState(recordIndex) == BroadcastRecord.DELIVERY_PENDING)) {
+            deferredStatesApplyConsumer.accept(record, recordIndex);
+        }
+
         if (record.isReplacePending()) {
             final BroadcastRecord replacedBroadcastRecord = replaceBroadcast(record, recordIndex,
                     wouldBeSkipped);
@@ -340,7 +355,12 @@
      * Predicates that choose to remove a broadcast <em>must</em> finish
      * delivery of the matched broadcast, to ensure that situations like ordered
      * broadcasts are handled consistently.
+     *
+     * @return if this operation may have changed internal state, indicating
+     *         that the caller is responsible for invoking
+     *         {@link BroadcastQueueModernImpl#updateRunnableList}
      */
+    @CheckResult
     public boolean forEachMatchingBroadcast(@NonNull BroadcastPredicate predicate,
             @NonNull BroadcastConsumer consumer, boolean andRemove) {
         boolean didSomething = false;
@@ -353,6 +373,7 @@
         return didSomething;
     }
 
+    @CheckResult
     private boolean forEachMatchingBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
             @NonNull BroadcastPredicate predicate, @NonNull BroadcastConsumer consumer,
             boolean andRemove) {
@@ -369,6 +390,10 @@
                     args.recycle();
                     it.remove();
                     onBroadcastDequeued(record, recordIndex, recordWouldBeSkipped);
+                } else {
+                    // Even if we're leaving broadcast in queue, it may have
+                    // been mutated in such a way to change our runnable time
+                    invalidateRunnableAt();
                 }
                 didSomething = true;
             }
@@ -380,32 +405,44 @@
 
     /**
      * Update the actively running "warm" process for this process.
+     *
+     * @return if this operation may have changed internal state, indicating
+     *         that the caller is responsible for invoking
+     *         {@link BroadcastQueueModernImpl#updateRunnableList}
      */
-    public void setProcessAndUidCached(@Nullable ProcessRecord app, boolean uidCached) {
+    @CheckResult
+    public boolean setProcessAndUidCached(@Nullable ProcessRecord app, boolean uidCached) {
         this.app = app;
-        if (app != null) {
-            setUidCached(uidCached);
-            setProcessInstrumented(app.getActiveInstrumentation() != null);
-            setProcessPersistent(app.isPersistent());
-        } else {
-            setUidCached(uidCached);
-            setProcessInstrumented(false);
-            setProcessPersistent(false);
-        }
 
         // Since we may have just changed our PID, invalidate cached strings
         mCachedToString = null;
         mCachedToShortString = null;
+
+        boolean didSomething = false;
+        if (app != null) {
+            didSomething |= setUidCached(uidCached);
+            didSomething |= setProcessInstrumented(app.getActiveInstrumentation() != null);
+            didSomething |= setProcessPersistent(app.isPersistent());
+        } else {
+            didSomething |= setUidCached(uidCached);
+            didSomething |= setProcessInstrumented(false);
+            didSomething |= setProcessPersistent(false);
+        }
+        return didSomething;
     }
 
     /**
      * Update if this process is in the "cached" state, typically signaling that
      * broadcast dispatch should be paused or delayed.
      */
-    private void setUidCached(boolean uidCached) {
+    @CheckResult
+    private boolean setUidCached(boolean uidCached) {
         if (mUidCached != uidCached) {
             mUidCached = uidCached;
             invalidateRunnableAt();
+            return true;
+        } else {
+            return false;
         }
     }
 
@@ -414,10 +451,14 @@
      * signaling that broadcast dispatch should bypass all pauses or delays, to
      * avoid holding up test suites.
      */
-    private void setProcessInstrumented(boolean instrumented) {
+    @CheckResult
+    private boolean setProcessInstrumented(boolean instrumented) {
         if (mProcessInstrumented != instrumented) {
             mProcessInstrumented = instrumented;
             invalidateRunnableAt();
+            return true;
+        } else {
+            return false;
         }
     }
 
@@ -425,10 +466,14 @@
      * Update if this process is in the "persistent" state, which signals broadcast dispatch should
      * bypass all pauses or delays to prevent the system from becoming out of sync with itself.
      */
-    private void setProcessPersistent(boolean persistent) {
+    @CheckResult
+    private boolean setProcessPersistent(boolean persistent) {
         if (mProcessPersistent != persistent) {
             mProcessPersistent = persistent;
             invalidateRunnableAt();
+            return true;
+        } else {
+            return false;
         }
     }
 
@@ -648,8 +693,20 @@
         return mActive != null;
     }
 
-    void forceDelayBroadcastDelivery(long delayedDurationMs) {
-        mForcedDelayedDurationMs = delayedDurationMs;
+    /**
+     * @return if this operation may have changed internal state, indicating
+     *         that the caller is responsible for invoking
+     *         {@link BroadcastQueueModernImpl#updateRunnableList}
+     */
+    @CheckResult
+    boolean forceDelayBroadcastDelivery(long delayedDurationMs) {
+        if (mForcedDelayedDurationMs != delayedDurationMs) {
+            mForcedDelayedDurationMs = delayedDurationMs;
+            invalidateRunnableAt();
+            return true;
+        } else {
+            return false;
+        }
     }
 
     /**
@@ -721,10 +778,21 @@
      * broadcasts would be prioritized for dispatching, even if there are urgent broadcasts
      * waiting. This is typically used in case there are callers waiting for "barrier" to be
      * reached.
+     *
+     * @return if this operation may have changed internal state, indicating
+     *         that the caller is responsible for invoking
+     *         {@link BroadcastQueueModernImpl#updateRunnableList}
      */
+    @CheckResult
     @VisibleForTesting
-    void setPrioritizeEarliest(boolean prioritizeEarliest) {
-        mPrioritizeEarliest = prioritizeEarliest;
+    boolean setPrioritizeEarliest(boolean prioritizeEarliest) {
+        if (mPrioritizeEarliest != prioritizeEarliest) {
+            mPrioritizeEarliest = prioritizeEarliest;
+            invalidateRunnableAt();
+            return true;
+        } else {
+            return false;
+        }
     }
 
     /**
@@ -912,9 +980,9 @@
     }
 
     /**
-     * Update {@link #getRunnableAt()} if it's currently invalidated.
+     * Update {@link #getRunnableAt()}, when needed.
      */
-    private void updateRunnableAt() {
+    void updateRunnableAt() {
         if (!mRunnableAtInvalidated) return;
         mRunnableAtInvalidated = false;
 
@@ -1027,10 +1095,44 @@
     }
 
     /**
+     * Update {@link BroadcastRecord.DELIVERY_DEFERRED} states of all our
+     * pending broadcasts, when needed.
+     */
+    void updateDeferredStates(@NonNull BroadcastConsumer applyConsumer,
+            @NonNull BroadcastConsumer clearConsumer) {
+        // When all we have pending is deferred broadcasts, and we're cached,
+        // then we want everything to be marked deferred
+        final boolean wantDeferredStates = (mCountDeferred > 0)
+                && (mCountDeferred == mCountEnqueued) && mUidCached;
+
+        if (mLastDeferredStates != wantDeferredStates) {
+            mLastDeferredStates = wantDeferredStates;
+            if (wantDeferredStates) {
+                forEachMatchingBroadcast((r, i) -> {
+                    return r.deferUntilActive
+                            && (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_PENDING);
+                }, applyConsumer, false);
+            } else {
+                forEachMatchingBroadcast((r, i) -> {
+                    return r.deferUntilActive
+                            && (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_DEFERRED);
+                }, clearConsumer, false);
+            }
+        }
+    }
+
+    /**
      * Check overall health, confirming things are in a reasonable state and
      * that we're not wedged.
      */
     public void assertHealthLocked() {
+        // If we're not actively running, we should be sorted into the runnable
+        // list, and if we're invalidated then someone likely forgot to invoke
+        // updateRunnableList() to re-sort us into place
+        if (!isActive()) {
+            checkState(!mRunnableAtInvalidated, "mRunnableAtInvalidated");
+        }
+
         assertHealthLocked(mPending);
         assertHealthLocked(mPendingUrgent);
         assertHealthLocked(mPendingOffload);
@@ -1133,19 +1235,30 @@
         return mCachedToShortString;
     }
 
+    public String describeStateLocked() {
+        return describeStateLocked(SystemClock.uptimeMillis());
+    }
+
+    public String describeStateLocked(@UptimeMillisLong long now) {
+        final StringBuilder sb = new StringBuilder();
+        if (isRunnable()) {
+            sb.append("runnable at ");
+            TimeUtils.formatDuration(getRunnableAt(), now, sb);
+        } else {
+            sb.append("not runnable");
+        }
+        sb.append(" because ");
+        sb.append(reasonToString(mRunnableAtReason));
+        return sb.toString();
+    }
+
     @NeverCompile
     public void dumpLocked(@UptimeMillisLong long now, @NonNull IndentingPrintWriter pw) {
         if ((mActive == null) && isEmpty()) return;
 
         pw.print(toShortString());
-        if (isRunnable()) {
-            pw.print(" runnable at ");
-            TimeUtils.formatDuration(getRunnableAt(), now, pw);
-        } else {
-            pw.print(" not runnable");
-        }
-        pw.print(" because ");
-        pw.print(reasonToString(mRunnableAtReason));
+        pw.print(" ");
+        pw.print(describeStateLocked(now));
         pw.println();
 
         pw.increaseIndent();
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index e532c15..8735f8a 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -327,6 +327,12 @@
             return;
         }
 
+        // To place ourselves correctly in the runnable list, we may need to
+        // update internals that may have been invalidated; we wait until now at
+        // the last possible moment to avoid duplicated work
+        queue.updateDeferredStates(mBroadcastConsumerDeferApply, mBroadcastConsumerDeferClear);
+        queue.updateRunnableAt();
+
         final boolean wantQueue = queue.isRunnable();
         final boolean inQueue = (queue == mRunnableHead) || (queue.runnableAtPrev != null)
                 || (queue.runnableAtNext != null);
@@ -352,8 +358,6 @@
         // If app isn't running, and there's nothing in the queue, clean up
         if (queue.isEmpty() && !queue.isActive() && !queue.isProcessWarm()) {
             removeProcessQueue(queue.processName, queue.uid);
-        } else {
-            updateQueueDeferred(queue);
         }
     }
 
@@ -619,14 +623,10 @@
             }
             enqueuedBroadcast = true;
             final BroadcastRecord replacedBroadcast = queue.enqueueOrReplaceBroadcast(
-                    r, i, wouldBeSkipped);
+                    r, i, wouldBeSkipped, mBroadcastConsumerDeferApply);
             if (replacedBroadcast != null) {
                 replacedBroadcasts.add(replacedBroadcast);
             }
-            if (r.isDeferUntilActive() && queue.isDeferredUntilActive()) {
-                setDeliveryState(queue, null, r, i, receiver, BroadcastRecord.DELIVERY_DEFERRED,
-                        "deferred at enqueue time");
-            }
             updateRunnableList(queue);
             enqueueUpdateRunningList();
         }
@@ -1249,14 +1249,14 @@
         r.resultExtras = null;
     };
 
-    private final BroadcastConsumer mBroadcastConsumerDefer = (r, i) -> {
+    private final BroadcastConsumer mBroadcastConsumerDeferApply = (r, i) -> {
         setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_DEFERRED,
-                "mBroadcastConsumerDefer");
+                "mBroadcastConsumerDeferApply");
     };
 
-    private final BroadcastConsumer mBroadcastConsumerUndoDefer = (r, i) -> {
+    private final BroadcastConsumer mBroadcastConsumerDeferClear = (r, i) -> {
         setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_PENDING,
-                "mBroadcastConsumerUndoDefer");
+                "mBroadcastConsumerDeferClear");
     };
 
     /**
@@ -1272,7 +1272,8 @@
                     final long now = SystemClock.uptimeMillis();
                     if (now > mLastTestFailureTime + DateUtils.SECOND_IN_MILLIS) {
                         mLastTestFailureTime = now;
-                        pw.println("Test " + label + " failed due to " + leaf.toShortString());
+                        pw.println("Test " + label + " failed due to " + leaf.toShortString() + " "
+                                + leaf.describeStateLocked());
                         pw.flush();
                     }
                     return false;
@@ -1309,34 +1310,25 @@
         return didSomething;
     }
 
-    private void forEachMatchingQueue(
+    private boolean forEachMatchingQueue(
             @NonNull Predicate<BroadcastProcessQueue> queuePredicate,
             @NonNull Consumer<BroadcastProcessQueue> queueConsumer) {
+        boolean didSomething = false;
         for (int i = mProcessQueues.size() - 1; i >= 0; i--) {
             BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
             while (leaf != null) {
                 if (queuePredicate.test(leaf)) {
                     queueConsumer.accept(leaf);
                     updateRunnableList(leaf);
+                    didSomething = true;
                 }
                 leaf = leaf.processNameNext;
             }
         }
-    }
-
-    private void updateQueueDeferred(
-            @NonNull BroadcastProcessQueue leaf) {
-        if (leaf.isDeferredUntilActive()) {
-            leaf.forEachMatchingBroadcast((r, i) -> {
-                return r.deferUntilActive && (r.getDeliveryState(i)
-                        == BroadcastRecord.DELIVERY_PENDING);
-            }, mBroadcastConsumerDefer, false);
-        } else if (leaf.hasDeferredBroadcasts()) {
-            leaf.forEachMatchingBroadcast((r, i) -> {
-                return r.deferUntilActive && (r.getDeliveryState(i)
-                        == BroadcastRecord.DELIVERY_DEFERRED);
-            }, mBroadcastConsumerUndoDefer, false);
+        if (didSomething) {
+            enqueueUpdateRunningList();
         }
+        return didSomething;
     }
 
     @Override
@@ -1359,8 +1351,6 @@
                         // Update internal state by refreshing values previously
                         // read from any known running process
                         setQueueProcess(leaf, leaf.app);
-                        updateQueueDeferred(leaf);
-                        updateRunnableList(leaf);
                         leaf = leaf.processNameNext;
                     }
                     enqueueUpdateRunningList();
@@ -1529,19 +1519,31 @@
         }
     }
 
+    @SuppressWarnings("CheckResult")
     private void updateWarmProcess(@NonNull BroadcastProcessQueue queue) {
         if (!queue.isProcessWarm()) {
-            setQueueProcess(queue, mService.getProcessRecordLocked(queue.processName, queue.uid));
+            // This is a bit awkward; we're in the middle of traversing the
+            // runnable queue, so we can't reorder that list if the runnable
+            // time changes here. However, if this process was just found to be
+            // warm via this operation, we're going to immediately promote it to
+            // be running, and any side effect of this operation will then apply
+            // after it's finished and is returned to the runnable list.
+            queue.setProcessAndUidCached(
+                    mService.getProcessRecordLocked(queue.processName, queue.uid),
+                    mUidCached.get(queue.uid, false));
         }
     }
 
     /**
      * Update the {@link ProcessRecord} associated with the given
-     * {@link BroadcastProcessQueue}.
+     * {@link BroadcastProcessQueue}. Also updates any runnable status that
+     * might have changed as a side-effect.
      */
     private void setQueueProcess(@NonNull BroadcastProcessQueue queue,
             @Nullable ProcessRecord app) {
-        queue.setProcessAndUidCached(app, mUidCached.get(queue.uid, false));
+        if (queue.setProcessAndUidCached(app, mUidCached.get(queue.uid, false))) {
+            updateRunnableList(queue);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index f42087f..8ad76cb 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -1326,8 +1326,7 @@
         UidRecord uidRec = app.getUidRecord();
         if (uidRec != null && uidRec.isFrozen()) {
             uidRec.setFrozen(false);
-            mFreezeHandler.removeMessages(UID_FROZEN_STATE_CHANGED_MSG, app);
-            reportOneUidFrozenStateChanged(app.uid, false);
+            postUidFrozenMessage(uidRec.getUid(), false);
         }
 
         opt.setFreezerOverride(false);
@@ -1468,8 +1467,7 @@
             UidRecord uidRec = app.getUidRecord();
             if (uidRec != null && uidRec.isFrozen()) {
                 uidRec.setFrozen(false);
-                mFreezeHandler.removeMessages(UID_FROZEN_STATE_CHANGED_MSG, app);
-                reportOneUidFrozenStateChanged(app.uid, false);
+                postUidFrozenMessage(uidRec.getUid(), false);
             }
 
             mFrozenProcesses.delete(app.getPid());
@@ -1998,6 +1996,15 @@
         mAm.reportUidFrozenStateChanged(uids, frozenStates);
     }
 
+    private void postUidFrozenMessage(int uid, boolean frozen) {
+        final Integer uidObj = Integer.valueOf(uid);
+        mFreezeHandler.removeEqualMessages(UID_FROZEN_STATE_CHANGED_MSG, uidObj);
+
+        final int op = frozen ? 1 : 0;
+        mFreezeHandler.sendMessage(mFreezeHandler.obtainMessage(UID_FROZEN_STATE_CHANGED_MSG, op,
+                0, uidObj));
+    }
+
     private final class FreezeHandler extends Handler implements
             ProcLocksReader.ProcLocksReaderCallback {
         private FreezeHandler() {
@@ -2028,7 +2035,9 @@
                     reportUnfreeze(pid, frozenDuration, processName, reason);
                     break;
                 case UID_FROZEN_STATE_CHANGED_MSG:
-                    reportOneUidFrozenStateChanged(((ProcessRecord) msg.obj).uid, true);
+                    final boolean frozen = (msg.arg1 == 1);
+                    final int uid = (int) msg.obj;
+                    reportOneUidFrozenStateChanged(uid, frozen);
                     break;
                 case DEADLOCK_WATCHDOG_MSG:
                     try {
@@ -2139,8 +2148,8 @@
                 final UidRecord uidRec = proc.getUidRecord();
                 if (frozen && uidRec != null && uidRec.areAllProcessesFrozen()) {
                     uidRec.setFrozen(true);
-                    mFreezeHandler.sendMessage(mFreezeHandler.obtainMessage(
-                            UID_FROZEN_STATE_CHANGED_MSG, proc));
+
+                    postUidFrozenMessage(uidRec.getUid(), true);
                 }
             }
 
diff --git a/services/core/java/com/android/server/am/DropboxRateLimiter.java b/services/core/java/com/android/server/am/DropboxRateLimiter.java
index 9ff2cd0..727d4df9 100644
--- a/services/core/java/com/android/server/am/DropboxRateLimiter.java
+++ b/services/core/java/com/android/server/am/DropboxRateLimiter.java
@@ -22,7 +22,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.expresslog.Counter;
+import com.android.modules.expresslog.Counter;
 
 /** Rate limiter for adding errors into dropbox. */
 public class DropboxRateLimiter {
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 70a696c..ca41f42 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -51,7 +51,7 @@
 import com.android.internal.annotations.CompositeRWLock;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.expresslog.Counter;
+import com.android.modules.expresslog.Counter;
 import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.TimeoutRecord;
 import com.android.internal.os.anr.AnrLatencyTracker;
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 7aae4d5..ffb40ee 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -579,7 +579,9 @@
         processName = _processName;
         sdkSandboxClientAppPackage = _sdkSandboxClientAppPackage;
         if (isSdkSandbox) {
-            sdkSandboxClientAppVolumeUuid = getClientInfoForSdkSandbox().volumeUuid;
+            final ApplicationInfo clientInfo = getClientInfoForSdkSandbox();
+            sdkSandboxClientAppVolumeUuid = clientInfo != null
+                    ? clientInfo.volumeUuid : null;
         } else {
             sdkSandboxClientAppVolumeUuid = null;
         }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 7ae31b2..50d375c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -212,6 +212,8 @@
         // 1) Authenticated == true
         // 2) Error occurred
         // 3) Authenticated == false
+        // 4) onLockout
+        // 5) onLockoutTimed
         mCallback.onClientFinished(this, true /* success */);
     }
 
@@ -304,11 +306,7 @@
         PerformanceTracker.getInstanceForSensorId(getSensorId())
                 .incrementTimedLockoutForUser(getTargetUserId());
 
-        try {
-            getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception", e);
-        }
+        onError(error, 0 /* vendorCode */);
     }
 
     @Override
@@ -323,10 +321,6 @@
         PerformanceTracker.getInstanceForSensorId(getSensorId())
                 .incrementPermanentLockoutForUser(getTargetUserId());
 
-        try {
-            getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception", e);
-        }
+        onError(error, 0 /* vendorCode */);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 1a53fec..c5037b7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -465,7 +465,7 @@
                         BaseClientMonitor clientMonitor,
                         boolean success) {
                     mAuthSessionCoordinator.authEndedFor(userId, Utils.getCurrentStrength(sensorId),
-                            sensorId, requestId, success);
+                            sensorId, requestId, client.wasAuthSuccessful());
                 }
             });
         });
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 273afcc..dff02bf 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -15,44 +15,53 @@
  */
 package com.android.server.notification;
 
+import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
+import static android.app.Notification.FLAG_AUTO_CANCEL;
+import static android.app.Notification.FLAG_GROUP_SUMMARY;
+import static android.app.Notification.FLAG_LOCAL_ONLY;
+import static android.app.Notification.FLAG_NO_CLEAR;
+import static android.app.Notification.FLAG_ONGOING_EVENT;
+
+import android.annotation.NonNull;
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.Map;
 
 /**
  * NotificationManagerService helper for auto-grouping notifications.
  */
 public class GroupHelper {
     private static final String TAG = "GroupHelper";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     protected static final String AUTOGROUP_KEY = "ranker_group";
 
+    // Flags that all autogroup summaries have
+    protected static final int BASE_FLAGS =
+            FLAG_AUTOGROUP_SUMMARY | FLAG_GROUP_SUMMARY | FLAG_LOCAL_ONLY;
+    // Flag that autogroup summaries inherits if all children have the flag
+    private static final int ALL_CHILDREN_FLAG = FLAG_AUTO_CANCEL;
+    // Flags that autogroup summaries inherits if any child has them
+    private static final int ANY_CHILDREN_FLAGS = FLAG_ONGOING_EVENT | FLAG_NO_CLEAR;
+
     private final Callback mCallback;
     private final int mAutoGroupAtCount;
 
-    // count the number of ongoing notifications per group
-    // userId|packageName -> (set of ongoing notifications that aren't in an app group)
-    final ArrayMap<String, ArraySet<String>>
-            mOngoingGroupCount = new ArrayMap<>();
-
-    // Map of user : <Map of package : notification keys>. Only contains notifications that are not
-    // grouped by the app (aka no group or sort key).
-    Map<Integer, Map<String, LinkedHashSet<String>>> mUngroupedNotifications = new HashMap<>();
+    // Only contains notifications that are not explicitly grouped by the app (aka no group or
+    // sort key).
+    // userId|packageName -> (keys of notifications that aren't in an explicit app group -> flags)
+    @GuardedBy("mUngroupedNotifications")
+    private final ArrayMap<String, ArrayMap<String, Integer>> mUngroupedNotifications
+            = new ArrayMap<>();
 
     public GroupHelper(int autoGroupAtCount, Callback callback) {
         mAutoGroupAtCount = autoGroupAtCount;
-        mCallback = callback;
+        mCallback =  callback;
     }
 
     private String generatePackageKey(int userId, String pkg) {
@@ -60,69 +69,30 @@
     }
 
     @VisibleForTesting
-    protected int getOngoingGroupCount(int userId, String pkg) {
-        String key = generatePackageKey(userId, pkg);
-        return mOngoingGroupCount.getOrDefault(key, new ArraySet<>(0)).size();
+    @GuardedBy("mUngroupedNotifications")
+    protected int getAutogroupSummaryFlags(@NonNull final ArrayMap<String, Integer> children) {
+        boolean allChildrenHasFlag = children.size() > 0;
+        int anyChildFlagSet = 0;
+        for (int i = 0; i < children.size(); i++) {
+            if (!hasAnyFlag(children.valueAt(i), ALL_CHILDREN_FLAG)) {
+                allChildrenHasFlag = false;
+            }
+            if (hasAnyFlag(children.valueAt(i), ANY_CHILDREN_FLAGS)) {
+                anyChildFlagSet |= (children.valueAt(i) & ANY_CHILDREN_FLAGS);
+            }
+        }
+        return BASE_FLAGS | (allChildrenHasFlag ? ALL_CHILDREN_FLAG : 0) | anyChildFlagSet;
     }
 
-    private void updateOngoingGroupCount(StatusBarNotification sbn, boolean add) {
-        if (sbn.getNotification().isGroupSummary()) {
-            return;
-        }
-        String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
-        ArraySet<String> notifications = mOngoingGroupCount.getOrDefault(key, new ArraySet<>(0));
-        if (add) {
-            notifications.add(sbn.getKey());
-            mOngoingGroupCount.put(key, notifications);
-        } else {
-            notifications.remove(sbn.getKey());
-            // we don't need to put it back if it is default
-        }
-
-        boolean needsOngoingFlag = notifications.size() > 0;
-        mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), needsOngoingFlag);
-    }
-
-    public void onNotificationUpdated(StatusBarNotification childSbn) {
-        updateOngoingGroupCount(childSbn, childSbn.isOngoing() && !childSbn.isAppGroup());
+    private boolean hasAnyFlag(int flags, int mask) {
+        return (flags & mask) != 0;
     }
 
     public void onNotificationPosted(StatusBarNotification sbn, boolean autogroupSummaryExists) {
         try {
-            updateOngoingGroupCount(sbn, sbn.isOngoing() && !sbn.isAppGroup());
-
-            List<String> notificationsToGroup = new ArrayList<>();
             if (!sbn.isAppGroup()) {
-                // Not grouped by the app, add to the list of notifications for the app;
-                // send grouping update if app exceeds the autogrouping limit.
-                synchronized (mUngroupedNotifications) {
-                    Map<String, LinkedHashSet<String>> ungroupedNotificationsByUser
-                            = mUngroupedNotifications.get(sbn.getUserId());
-                    if (ungroupedNotificationsByUser == null) {
-                        ungroupedNotificationsByUser = new HashMap<>();
-                    }
-                    mUngroupedNotifications.put(sbn.getUserId(), ungroupedNotificationsByUser);
-                    LinkedHashSet<String> notificationsForPackage
-                            = ungroupedNotificationsByUser.get(sbn.getPackageName());
-                    if (notificationsForPackage == null) {
-                        notificationsForPackage = new LinkedHashSet<>();
-                    }
-
-                    notificationsForPackage.add(sbn.getKey());
-                    ungroupedNotificationsByUser.put(sbn.getPackageName(), notificationsForPackage);
-
-                    if (notificationsForPackage.size() >= mAutoGroupAtCount
-                            || autogroupSummaryExists) {
-                        notificationsToGroup.addAll(notificationsForPackage);
-                    }
-                }
-                if (notificationsToGroup.size() > 0) {
-                    adjustAutogroupingSummary(sbn.getUserId(), sbn.getPackageName(),
-                            notificationsToGroup.get(0), true);
-                    adjustNotificationBundling(notificationsToGroup, true);
-                }
+                maybeGroup(sbn, autogroupSummaryExists);
             } else {
-                // Grouped, but not by us. Send updates to un-autogroup, if we grouped it.
                 maybeUngroup(sbn, false, sbn.getUserId());
             }
 
@@ -133,7 +103,6 @@
 
     public void onNotificationRemoved(StatusBarNotification sbn) {
         try {
-            updateOngoingGroupCount(sbn, false);
             maybeUngroup(sbn, true, sbn.getUserId());
         } catch (Exception e) {
             Slog.e(TAG, "Error processing canceled notification", e);
@@ -141,70 +110,114 @@
     }
 
     /**
-     * Un-autogroups notifications that are now grouped by the app.
+     * A non-app grouped notification has been added or updated
+     * Evaluate if:
+     * (a) an existing autogroup summary needs updated flags
+     * (b) a new autogroup summary needs to be added with correct flags
+     * (c) other non-app grouped children need to be moved to the autogroup
+     *
+     * And stores the list of upgrouped notifications & their flags
+     */
+    private void maybeGroup(StatusBarNotification sbn, boolean autogroupSummaryExists) {
+        int flags = 0;
+        List<String> notificationsToGroup = new ArrayList<>();
+        synchronized (mUngroupedNotifications) {
+            String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
+            final ArrayMap<String, Integer> children =
+                    mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
+
+            children.put(sbn.getKey(), sbn.getNotification().flags);
+            mUngroupedNotifications.put(key, children);
+
+            if (children.size() >= mAutoGroupAtCount || autogroupSummaryExists) {
+                flags = getAutogroupSummaryFlags(children);
+                notificationsToGroup.addAll(children.keySet());
+            }
+        }
+        if (notificationsToGroup.size() > 0) {
+            if (autogroupSummaryExists) {
+                mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), flags);
+            } else {
+                mCallback.addAutoGroupSummary(
+                        sbn.getUserId(), sbn.getPackageName(), sbn.getKey(), flags);
+            }
+            for (String key : notificationsToGroup) {
+                mCallback.addAutoGroup(key);
+            }
+        }
+    }
+
+    /**
+     * A notification was added that's app grouped, or a notification was removed.
+     * Evaluate whether:
+     * (a) an existing autogroup summary needs updated flags
+     * (b) if we need to remove our autogroup overlay for this notification
+     * (c) we need to remove the autogroup summary
+     *
+     * And updates the internal state of un-app-grouped notifications and their flags
      */
     private void maybeUngroup(StatusBarNotification sbn, boolean notificationGone, int userId) {
-        List<String> notificationsToUnAutogroup = new ArrayList<>();
         boolean removeSummary = false;
+        int summaryFlags = 0;
+        boolean updateSummaryFlags = false;
+        boolean removeAutogroupOverlay = false;
         synchronized (mUngroupedNotifications) {
-            Map<String, LinkedHashSet<String>> ungroupedNotificationsByUser
-                    = mUngroupedNotifications.get(sbn.getUserId());
-            if (ungroupedNotificationsByUser == null || ungroupedNotificationsByUser.size() == 0) {
+            String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
+            final ArrayMap<String, Integer> children =
+                    mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
+            if (children.size() == 0) {
                 return;
             }
-            LinkedHashSet<String> notificationsForPackage
-                    = ungroupedNotificationsByUser.get(sbn.getPackageName());
-            if (notificationsForPackage == null || notificationsForPackage.size() == 0) {
-                return;
-            }
-            if (notificationsForPackage.remove(sbn.getKey())) {
-                if (!notificationGone) {
-                    // Add the current notification to the ungrouping list if it still exists.
-                    notificationsToUnAutogroup.add(sbn.getKey());
+
+            // if this notif was autogrouped and now isn't
+            if (children.containsKey(sbn.getKey())) {
+                // if this notification was contributing flags that aren't covered by other
+                // children to the summary, reevaluate flags for the summary
+                int flags = children.remove(sbn.getKey());
+                // this
+                if (hasAnyFlag(flags, ANY_CHILDREN_FLAGS)) {
+                    updateSummaryFlags = true;
+                    summaryFlags = getAutogroupSummaryFlags(children);
                 }
-            }
-            // If the status change of this notification has brought the number of loose
-            // notifications to zero, remove the summary and un-autogroup.
-            if (notificationsForPackage.size() == 0) {
-                ungroupedNotificationsByUser.remove(sbn.getPackageName());
-                removeSummary = true;
+                // if this notification still exists and has an autogroup overlay, but is now
+                // grouped by the app, clear the overlay
+                if (!notificationGone && sbn.getOverrideGroupKey() != null) {
+                    removeAutogroupOverlay = true;
+                }
+
+                // If there are no more children left to autogroup, remove the summary
+                if (children.size() == 0) {
+                    removeSummary = true;
+                }
             }
         }
         if (removeSummary) {
-            adjustAutogroupingSummary(userId, sbn.getPackageName(), null, false);
-        }
-        if (notificationsToUnAutogroup.size() > 0) {
-            adjustNotificationBundling(notificationsToUnAutogroup, false);
-        }
-    }
-
-    private void adjustAutogroupingSummary(int userId, String packageName, String triggeringKey,
-            boolean summaryNeeded) {
-        if (summaryNeeded) {
-            mCallback.addAutoGroupSummary(userId, packageName, triggeringKey,
-                    getOngoingGroupCount(userId, packageName) > 0);
+            mCallback.removeAutoGroupSummary(userId, sbn.getPackageName());
         } else {
-            mCallback.removeAutoGroupSummary(userId, packageName);
+            if (updateSummaryFlags) {
+                mCallback.updateAutogroupSummary(userId, sbn.getPackageName(), summaryFlags);
+            }
+        }
+        if (removeAutogroupOverlay) {
+            mCallback.removeAutoGroup(sbn.getKey());
         }
     }
 
-    private void adjustNotificationBundling(List<String> keys, boolean group) {
-        for (String key : keys) {
-            if (DEBUG) Log.i(TAG, "Sending grouping adjustment for: " + key + " group? " + group);
-            if (group) {
-                mCallback.addAutoGroup(key);
-            } else {
-                mCallback.removeAutoGroup(key);
-            }
+    @VisibleForTesting
+    int getNotGroupedByAppCount(int userId, String pkg) {
+        synchronized (mUngroupedNotifications) {
+            String key = generatePackageKey(userId, pkg);
+            final ArrayMap<String, Integer> children =
+                    mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
+            return children.size();
         }
     }
 
     protected interface Callback {
         void addAutoGroup(String key);
         void removeAutoGroup(String key);
-        void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
-                boolean needsOngoingFlag);
+        void addAutoGroupSummary(int userId, String pkg, String triggeringKey, int flags);
         void removeAutoGroupSummary(int user, String pkg);
-        void updateAutogroupSummary(int userId, String pkg, boolean needsOngoingFlag);
+        void updateAutogroupSummary(int userId, String pkg, int flags);
     }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 5d81dda..1301cd47 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -319,6 +319,7 @@
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.policy.PermissionPolicyInternal;
+import com.android.server.powerstats.StatsPullAtomCallbackImpl;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.utils.Slogf;
@@ -902,11 +903,11 @@
      * has the same flag. It will delete the flag otherwise
      * @param userId user id of the autogroup summary
      * @param pkg package of the autogroup summary
-     * @param needsOngoingFlag true if the group has at least one ongoing notification
+     * @param flags the new flags for this summary
      * @param isAppForeground true if the app is currently in the foreground.
      */
     @GuardedBy("mNotificationLock")
-    protected void updateAutobundledSummaryFlags(int userId, String pkg, boolean needsOngoingFlag,
+    protected void updateAutobundledSummaryFlags(int userId, String pkg, int flags,
             boolean isAppForeground) {
         ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
         if (summaries == null) {
@@ -921,13 +922,8 @@
             return;
         }
         int oldFlags = summary.getSbn().getNotification().flags;
-        if (needsOngoingFlag) {
-            summary.getSbn().getNotification().flags |= FLAG_ONGOING_EVENT;
-        } else {
-            summary.getSbn().getNotification().flags &= ~FLAG_ONGOING_EVENT;
-        }
-
-        if (summary.getSbn().getNotification().flags != oldFlags) {
+        if (oldFlags != flags) {
+            summary.getSbn().getNotification().flags = flags;
             mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground,
                     SystemClock.elapsedRealtime()));
         }
@@ -2684,9 +2680,14 @@
 
             @Override
             public void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
-                    boolean needsOngoingFlag) {
-                NotificationManagerService.this.addAutoGroupSummary(
-                        userId, pkg, triggeringKey, needsOngoingFlag);
+                    int flags) {
+                NotificationRecord r = createAutoGroupSummary(userId, pkg, triggeringKey, flags);
+                if (r != null) {
+                    final boolean isAppForeground =
+                            mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
+                    mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground,
+                            SystemClock.elapsedRealtime()));
+                }
             }
 
             @Override
@@ -2697,11 +2698,11 @@
             }
 
             @Override
-            public void updateAutogroupSummary(int userId, String pkg, boolean needsOngoingFlag) {
+            public void updateAutogroupSummary(int userId, String pkg, int flags) {
                 boolean isAppForeground = pkg != null
                         && mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
                 synchronized (mNotificationLock) {
-                    updateAutobundledSummaryFlags(userId, pkg, needsOngoingFlag, isAppForeground);
+                    updateAutobundledSummaryFlags(userId, pkg, flags, isAppForeground);
                 }
             }
         });
@@ -5961,19 +5962,6 @@
         r.addAdjustment(adjustment);
     }
 
-    @VisibleForTesting
-    void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
-            boolean needsOngoingFlag) {
-        NotificationRecord r = createAutoGroupSummary(
-                userId, pkg, triggeringKey, needsOngoingFlag);
-        if (r != null) {
-            final boolean isAppForeground =
-                    mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
-            mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground,
-                    SystemClock.elapsedRealtime()));
-        }
-    }
-
     // Clears the 'fake' auto-group summary.
     @VisibleForTesting
     @GuardedBy("mNotificationLock")
@@ -5997,7 +5985,7 @@
 
     // Creates a 'fake' summary for a package that has exceeded the solo-notification limit.
     NotificationRecord createAutoGroupSummary(int userId, String pkg, String triggeringKey,
-            boolean needsOngoingFlag) {
+            int flagsToSet) {
         NotificationRecord summaryRecord = null;
         boolean isPermissionFixed = mPermissionHelper.isPermissionFixed(pkg, userId);
         synchronized (mNotificationLock) {
@@ -6007,7 +5995,6 @@
                 // adjustment will post a summary if needed.
                 return null;
             }
-            NotificationChannel channel = notificationRecord.getChannel();
             final StatusBarNotification adjustedSbn = notificationRecord.getSbn();
             userId = adjustedSbn.getUser().getIdentifier();
             int uid =  adjustedSbn.getUid();
@@ -6030,11 +6017,8 @@
                                 .setGroupSummary(true)
                                 .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
                                 .setGroup(GroupHelper.AUTOGROUP_KEY)
-                                .setFlag(FLAG_AUTOGROUP_SUMMARY, true)
-                                .setFlag(Notification.FLAG_GROUP_SUMMARY, true)
-                                .setFlag(FLAG_ONGOING_EVENT, needsOngoingFlag)
+                                .setFlag(flagsToSet, true)
                                 .setColor(adjustedSbn.getNotification().color)
-                                .setLocalOnly(true)
                                 .build();
                 summaryNotification.extras.putAll(extras);
                 Intent appIntent = getContext().getPackageManager().getLaunchIntentForPackage(pkg);
@@ -6372,6 +6356,7 @@
      * The private API only accessible to the system process.
      */
     private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() {
+
         @Override
         public NotificationChannel getNotificationChannel(String pkg, int uid, String
                 channelId) {
@@ -7827,18 +7812,17 @@
                     if (notification.getSmallIcon() != null) {
                         StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
                         mListeners.notifyPostedLocked(r, old);
-                        if ((oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup()))
-                                && !isCritical(r)) {
-                            mHandler.post(() -> {
-                                synchronized (mNotificationLock) {
-                                    mGroupHelper.onNotificationPosted(
-                                            n, hasAutoGroupSummaryLocked(n));
-                                }
-                            });
-                        } else if (oldSbn != null) {
-                            final NotificationRecord finalRecord = r;
-                            mHandler.post(() ->
-                                    mGroupHelper.onNotificationUpdated(finalRecord.getSbn()));
+                        if (oldSbn == null
+                                || !Objects.equals(oldSbn.getGroup(), n.getGroup())
+                                || oldSbn.getNotification().flags != n.getNotification().flags) {
+                            if (!isCritical(r)) {
+                                mHandler.post(() -> {
+                                    synchronized (mNotificationLock) {
+                                        mGroupHelper.onNotificationPosted(
+                                                n, hasAutoGroupSummaryLocked(n));
+                                    }
+                                });
+                            }
                         }
                     } else {
                         Slog.e(TAG, "Not posting notification without small icon: " + notification);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 596e9b9..69ef3f7 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -994,7 +994,7 @@
                     reconciledPackages = ReconcilePackageUtils.reconcilePackages(
                             requests, Collections.unmodifiableMap(mPm.mPackages),
                             versionInfos, mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
-                            mPm.mSettings);
+                            mPm.mSettings, mContext);
                 } catch (ReconcileFailure e) {
                     for (InstallRequest request : requests) {
                         request.setError("Reconciliation failed...", e);
@@ -3930,7 +3930,7 @@
                                 mPm.mPackages, Collections.singletonMap(pkgName,
                                         mPm.getSettingsVersionForPackage(parsedPackage)),
                                 mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
-                                mPm.mSettings);
+                                mPm.mSettings, mContext);
                 if ((scanFlags & SCAN_AS_APEX) == 0) {
                     appIdCreated = optimisticallyRegisterAppId(installRequest);
                 } else {
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index 5312ae6..e3c97e9 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
 import static android.content.pm.SigningDetails.CapabilityMergeRule.MERGE_RESTRICTED_CAPABILITY;
@@ -23,19 +24,24 @@
 import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
 import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
 
+import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.SigningDetails;
 import android.os.SystemProperties;
+import android.permission.PermissionManager;
 import android.util.ArrayMap;
 import android.util.Log;
 
 import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.component.ParsedUsesPermission;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.utils.WatchedLongSparseArray;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
@@ -54,7 +60,7 @@
             Map<String, AndroidPackage> allPackages,
             Map<String, Settings.VersionInfo> versionInfos,
             SharedLibrariesImpl sharedLibraries,
-            KeySetManagerService ksms, Settings settings)
+            KeySetManagerService ksms, Settings settings, Context context)
             throws ReconcileFailure {
         final List<ReconciledPackage> result = new ArrayList<>(installRequests.size());
 
@@ -143,11 +149,11 @@
                 } else {
                     if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
                         throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
-                                "Package " + parsedPackage.getPackageName()
+                                "Package " + installPackageName
                                         + " upgrade keys do not match the previously installed"
                                         + " version");
                     } else {
-                        String msg = "System package " + parsedPackage.getPackageName()
+                        String msg = "System package " + installPackageName
                                 + " signature changed; retaining data.";
                         PackageManagerService.reportSettingsProblem(Log.WARN, msg);
                     }
@@ -168,11 +174,42 @@
                         removeAppKeySetData = true;
                     }
 
-                    // if this is is a sharedUser, check to see if the new package is signed by a
-                    // newer
-                    // signing certificate than the existing one, and if so, copy over the new
+                    // if this is a sharedUser, check to see if the new package is signed by a
+                    // newer signing certificate than the existing one, and if so, copy over the new
                     // details
                     if (sharedUserSetting != null) {
+                        if (!parsedPackage.isTestOnly() && sharedUserSetting.isPrivileged()
+                                && !signatureCheckPs.isSystem()) {
+                            final List<ParsedUsesPermission> usesPermissions =
+                                    parsedPackage.getUsesPermissions();
+                            final List<String> usesPrivilegedPermissions = new ArrayList<>();
+                            final PermissionManager permissionManager = context.getSystemService(
+                                    PermissionManager.class);
+                            // Check if the app requests any privileged permissions because that
+                            // violates the privapp-permissions allowlist check during boot.
+                            if (permissionManager != null) {
+                                for (int i = 0; i < usesPermissions.size(); i++) {
+                                    final String permissionName = usesPermissions.get(i).getName();
+                                    final PermissionInfo permissionInfo =
+                                            permissionManager.getPermissionInfo(permissionName, 0);
+                                    if (permissionInfo != null
+                                            && (permissionInfo.getProtectionFlags()
+                                            & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0) {
+                                        usesPrivilegedPermissions.add(permissionName);
+                                    }
+                                }
+                            }
+
+                            if (!usesPrivilegedPermissions.isEmpty()) {
+                                throw new ReconcileFailure(INSTALL_FAILED_INVALID_APK,
+                                        "Non-system package: " + installPackageName
+                                                + " shares signature and sharedUserId with"
+                                                + " a privileged package but requests"
+                                                + " privileged permissions that are not"
+                                                + " allowed: " + Arrays.toString(
+                                                        usesPrivilegedPermissions.toArray()));
+                            }
+                        }
                         // Attempt to merge the existing lineage for the shared SigningDetails with
                         // the lineage of the new package; if the shared SigningDetails are not
                         // returned this indicates the new package added new signers to the lineage
@@ -189,7 +226,7 @@
                             for (AndroidPackage androidPackage : sharedUserSetting.getPackages()) {
                                 if (androidPackage.getPackageName() != null
                                         && !androidPackage.getPackageName().equals(
-                                        parsedPackage.getPackageName())) {
+                                                installPackageName)) {
                                     mergedDetails = mergedDetails.mergeLineageWith(
                                             androidPackage.getSigningDetails(),
                                             MERGE_RESTRICTED_CAPABILITY);
@@ -219,7 +256,7 @@
                     if (sharedUserSetting != null) {
                         if (sharedUserSetting.signaturesChanged != null
                                 && !PackageManagerServiceUtils.canJoinSharedUserId(
-                                parsedPackage.getPackageName(), parsedPackage.getSigningDetails(),
+                                installPackageName, parsedPackage.getSigningDetails(),
                                 sharedUserSetting,
                                 PackageManagerServiceUtils.SHARED_USER_ID_JOIN_TYPE_SYSTEM)) {
                             if (SystemProperties.getInt("ro.product.first_api_level", 0) <= 29) {
@@ -240,7 +277,7 @@
                                 // whichever package happened to be scanned later.
                                 throw new IllegalStateException(
                                         "Signature mismatch on system package "
-                                                + parsedPackage.getPackageName()
+                                                + installPackageName
                                                 + " for shared user "
                                                 + sharedUserSetting);
                             }
@@ -252,7 +289,7 @@
                         sharedUserSetting.signaturesChanged = Boolean.TRUE;
                     }
                     // File a report about this.
-                    String msg = "System package " + parsedPackage.getPackageName()
+                    String msg = "System package " + installPackageName
                             + " signature changed; retaining data.";
                     PackageManagerService.reportSettingsProblem(Log.WARN, msg);
                 } catch (IllegalArgumentException e) {
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index e5e32f0..4d2b119 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -206,8 +206,6 @@
     static {
         SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
         SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
-        SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE);
-        SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND);
     }
 
     private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>();
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index c5f939a..297ad73 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1332,9 +1332,7 @@
                     // Bg location is one-off runtime modifier permission and has no app op
                     if (sPlatformPermissions.containsKey(permission)
                             && !Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission)
-                            && !Manifest.permission.BODY_SENSORS_BACKGROUND.equals(permission)
-                            && !Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND
-                            .equals(permission)) {
+                            && !Manifest.permission.BODY_SENSORS_BACKGROUND.equals(permission)) {
                         Slog.wtf(LOG_TAG, "Platform runtime permission " + permission
                                 + " with no app op defined!");
                     }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index cc2c9ad..3492b26 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -23,6 +23,7 @@
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_ERRORED;
 import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DEFAULT;
+import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DENIED;
 import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED;
 import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
@@ -3655,6 +3656,26 @@
 
         for (String permission : pkg.getRequestedPermissions()) {
             Integer permissionState = permissionStates.get(permission);
+
+            if (Objects.equals(permission, Manifest.permission.USE_FULL_SCREEN_INTENT)
+                    && permissionState == null) {
+                final PackageStateInternal ps;
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    ps = mPackageManagerInt.getPackageStateInternal(pkg.getPackageName());
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+                final String[] useFullScreenIntentPackageNames =
+                        mContext.getResources().getStringArray(
+                                com.android.internal.R.array.config_useFullScreenIntentPackages);
+                final boolean canUseFullScreenIntent = (ps != null && ps.isSystem())
+                        || ArrayUtils.contains(useFullScreenIntentPackageNames,
+                                pkg.getPackageName());
+                permissionState = canUseFullScreenIntent ? PERMISSION_STATE_GRANTED
+                        : PERMISSION_STATE_DENIED;
+            }
+
             if (permissionState == null || permissionState == PERMISSION_STATE_DEFAULT) {
                 continue;
             }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 8165958..fc6b4e9 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -222,6 +222,7 @@
 import com.android.server.policy.keyguard.KeyguardStateMonitor.StateCallback;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.vr.VrManagerInternal;
+import com.android.server.wallpaper.WallpaperManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.DisplayPolicy;
 import com.android.server.wm.DisplayRotation;
@@ -412,6 +413,9 @@
     SensorPrivacyManager mSensorPrivacyManager;
     DisplayManager mDisplayManager;
     DisplayManagerInternal mDisplayManagerInternal;
+
+    private WallpaperManagerInternal mWallpaperManagerInternal;
+
     boolean mPreloadedRecentApps;
     final Object mServiceAcquireLock = new Object();
     Vibrator mVibrator; // Vibrator for giving feedback of orientation changes
@@ -5016,11 +5020,34 @@
         return bootCompleted ? mKeyguardDrawnTimeout : 5000;
     }
 
+    @Nullable
+    private WallpaperManagerInternal getWallpaperManagerInternal() {
+        if (mWallpaperManagerInternal == null) {
+            mWallpaperManagerInternal = LocalServices.getService(WallpaperManagerInternal.class);
+        }
+        return mWallpaperManagerInternal;
+    }
+
+    private void reportScreenTurningOnToWallpaper(int displayId) {
+        WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal();
+        if (wallpaperManagerInternal != null) {
+            wallpaperManagerInternal.onScreenTurningOn(displayId);
+        }
+    }
+
+    private void reportScreenTurnedOnToWallpaper(int displayId) {
+        WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal();
+        if (wallpaperManagerInternal != null) {
+            wallpaperManagerInternal.onScreenTurnedOn(displayId);
+        }
+    }
+
     // Called on the DisplayManager's DisplayPowerController thread.
     @Override
     public void screenTurningOn(int displayId, final ScreenOnListener screenOnListener) {
         if (DEBUG_WAKEUP) Slog.i(TAG, "Display " + displayId + " turning on...");
 
+        reportScreenTurningOnToWallpaper(displayId);
         if (displayId == DEFAULT_DISPLAY) {
             Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn",
                     0 /* cookie */);
@@ -5061,6 +5088,8 @@
     public void screenTurnedOn(int displayId) {
         if (DEBUG_WAKEUP) Slog.i(TAG, "Display " + displayId + " turned on...");
 
+        reportScreenTurnedOnToWallpaper(displayId);
+
         if (displayId != DEFAULT_DISPLAY) {
             return;
         }
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 7beb1ed..e437be8 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -105,36 +105,46 @@
     @Override
     public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
             @FailureReasons int failureReason, int mitigationCount) {
-        // For native crashes, we will roll back any available rollbacks
+        boolean anyRollbackAvailable = !mContext.getSystemService(RollbackManager.class)
+                .getAvailableRollbacks().isEmpty();
+        int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+
         if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH
-                && !mContext.getSystemService(RollbackManager.class)
-                .getAvailableRollbacks().isEmpty()) {
-            return PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
-        }
-        if (getAvailableRollback(failedPackage) == null) {
-            // Don't handle the notification, no rollbacks available for the package
-            return PackageHealthObserverImpact.USER_IMPACT_NONE;
-        } else {
+                && anyRollbackAvailable) {
+            // For native crashes, we will directly roll back any available rollbacks
+            // Note: For non-native crashes the rollback-all step has higher impact
+            impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+        } else if (mitigationCount == 1 && getAvailableRollback(failedPackage) != null) {
             // Rollback is available, we may get a callback into #execute
-            return PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
+            impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+        } else if (mitigationCount > 1 && anyRollbackAvailable) {
+            // If any rollbacks are available, we will commit them
+            impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
         }
+
+        return impact;
     }
 
     @Override
     public boolean execute(@Nullable VersionedPackage failedPackage,
             @FailureReasons int rollbackReason, int mitigationCount) {
         if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
-            mHandler.post(() -> rollbackAll());
+            mHandler.post(() -> rollbackAll(rollbackReason));
             return true;
         }
 
-        RollbackInfo rollback = getAvailableRollback(failedPackage);
-        if (rollback == null) {
-            Slog.w(TAG, "Expected rollback but no valid rollback found for " + failedPackage);
-            return false;
+        if (mitigationCount == 1) {
+            RollbackInfo rollback = getAvailableRollback(failedPackage);
+            if (rollback == null) {
+                Slog.w(TAG, "Expected rollback but no valid rollback found for " + failedPackage);
+                return false;
+            }
+            mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
+        } else if (mitigationCount > 1) {
+            mHandler.post(() -> rollbackAll(rollbackReason));
         }
-        mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
-        // Assume rollback executed successfully
+
+        // Assume rollbacks executed successfully
         return true;
     }
 
@@ -468,7 +478,7 @@
     }
 
     @WorkerThread
-    private void rollbackAll() {
+    private void rollbackAll(@FailureReasons int rollbackReason) {
         assertInWorkerThread();
         RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
         List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks();
@@ -487,7 +497,7 @@
 
         for (RollbackInfo rollback : rollbacks) {
             VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom();
-            rollbackPackage(rollback, sample, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
+            rollbackPackage(rollback, sample, rollbackReason);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
index 584fbdd..3699557 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
@@ -27,4 +27,10 @@
      * Notifies the display is ready for adding wallpaper on it.
      */
     public abstract void onDisplayReady(int displayId);
+
+    /** Notifies when the screen finished turning on and is visible to the user. */
+    public abstract void onScreenTurnedOn(int displayId);
+
+    /** Notifies when the screen starts turning on and is not yet visible to the user. */
+    public abstract void onScreenTurningOn(int displayId);
 }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 4a03628..e178669 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1613,6 +1613,15 @@
         public void onDisplayReady(int displayId) {
             onDisplayReadyInternal(displayId);
         }
+
+        @Override
+        public void onScreenTurnedOn(int displayId) {
+            notifyScreenTurnedOn(displayId);
+        }
+        @Override
+        public void onScreenTurningOn(int displayId) {
+            notifyScreenTurningOn(displayId);
+        }
     }
 
     void initialize() {
@@ -2442,6 +2451,54 @@
         }
     }
 
+    /**
+     * Propagates screen turned on event to wallpaper engine.
+     */
+    @Override
+    public void notifyScreenTurnedOn(int displayId) {
+        synchronized (mLock) {
+            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
+            if (data != null
+                    && data.connection != null
+                    && data.connection.containsDisplay(displayId)) {
+                final IWallpaperEngine engine = data.connection
+                        .getDisplayConnectorOrCreate(displayId).mEngine;
+                if (engine != null) {
+                    try {
+                        engine.onScreenTurnedOn();
+                    } catch (RemoteException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+    }
+
+
+
+    /**
+     * Propagate screen turning on event to wallpaper engine.
+     */
+    @Override
+    public void notifyScreenTurningOn(int displayId) {
+        synchronized (mLock) {
+            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
+            if (data != null
+                    && data.connection != null
+                    && data.connection.containsDisplay(displayId)) {
+                final IWallpaperEngine engine = data.connection
+                        .getDisplayConnectorOrCreate(displayId).mEngine;
+                if (engine != null) {
+                    try {
+                        engine.onScreenTurningOn();
+                    } catch (RemoteException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+    }
+
     @Override
     public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
         checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 6214440..7718dd8 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1145,8 +1145,7 @@
         if (mService.mWindowManager.mSyncEngine.hasActiveSync()) {
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                     "Creating Pending Multiwindow Fullscreen Request: %s", transition);
-            mService.mWindowManager.mSyncEngine.queueSyncSet(
-                    () -> r.mTransitionController.moveToCollecting(transition),
+            r.mTransitionController.queueCollecting(transition,
                     () -> {
                         executeFullscreenRequestTransition(fullscreenRequest, callback, r,
                                 transition, true /* queued */);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1bcc05e..0b98495 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -9820,8 +9820,7 @@
                 scheduleStopForRestartProcess();
             };
             if (mWmService.mSyncEngine.hasActiveSync()) {
-                mWmService.mSyncEngine.queueSyncSet(
-                        () -> mTransitionController.moveToCollecting(transition), executeRestart);
+                mTransitionController.queueCollecting(transition, executeRestart);
             } else {
                 mTransitionController.moveToCollecting(transition);
                 executeRestart.run();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 8123c07..e780716 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2874,8 +2874,7 @@
                 final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */,
                         getTransitionController(), mWindowManager.mSyncEngine);
                 if (mWindowManager.mSyncEngine.hasActiveSync()) {
-                    mWindowManager.mSyncEngine.queueSyncSet(
-                            () -> getTransitionController().moveToCollecting(transition),
+                    getTransitionController().queueCollecting(transition,
                             () -> {
                                 if (!task.getWindowConfiguration().canResizeTask()) {
                                     Slog.w(TAG, "resizeTask not allowed on task=" + task);
@@ -3629,9 +3628,7 @@
                     if (transition != null && mWindowManager.mSyncEngine.hasActiveSync()) {
                         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                                 "Creating Pending Pip-Enter: %s", transition);
-                        mWindowManager.mSyncEngine.queueSyncSet(
-                                () -> getTransitionController().moveToCollecting(transition),
-                                enterPipRunnable);
+                        getTransitionController().queueCollecting(transition, enterPipRunnable);
                     } else {
                         // Move to collecting immediately to "claim" the sync-engine for this
                         // transition.
@@ -3647,9 +3644,7 @@
             if (transition != null && mWindowManager.mSyncEngine.hasActiveSync()) {
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                         "Creating Pending Pip-Enter: %s", transition);
-                mWindowManager.mSyncEngine.queueSyncSet(
-                        () -> getTransitionController().moveToCollecting(transition),
-                        enterPipRunnable);
+                getTransitionController().queueCollecting(transition, enterPipRunnable);
             } else {
                 if (transition != null) {
                     getTransitionController().moveToCollecting(transition);
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index efde703..11d84ff 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -645,31 +645,28 @@
         if (finishedTransition == mWaitTransitionFinish) {
             clearBackAnimations();
         }
+
         if (!mBackAnimationInProgress || mPendingAnimationBuilder == null) {
             return false;
         }
-
         ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
                 "Handling the deferred animation after transition finished");
 
-        // Show the target surface and its parents to prevent it or its parents hidden when
-        // the transition finished.
-        // The target could be affected by transition when :
-        // Open transition -> the open target in back navigation
-        // Close transition -> the close target in back navigation.
+        // Find the participated container collected by transition when :
+        // Open transition -> the open target in back navigation, the close target in transition.
+        // Close transition -> the close target in back navigation, the open target in transition.
         boolean hasTarget = false;
-        final SurfaceControl.Transaction t =
-                mPendingAnimationBuilder.mCloseTarget.getPendingTransaction();
-        for (int i = 0; i < targets.size(); i++) {
-            final WindowContainer wc = targets.get(i).mContainer;
-            if (wc.asActivityRecord() == null && wc.asTask() == null) {
-                continue;
-            } else if (!mPendingAnimationBuilder.containTarget(wc)) {
+        for (int i = 0; i < finishedTransition.mParticipants.size(); i++) {
+            final WindowContainer wc = finishedTransition.mParticipants.valueAt(i);
+            if (wc.asActivityRecord() == null && wc.asTask() == null
+                    && wc.asTaskFragment() == null) {
                 continue;
             }
 
-            hasTarget = true;
-            t.show(wc.getSurfaceControl());
+            if (mPendingAnimationBuilder.containTarget(wc)) {
+                hasTarget = true;
+                break;
+            }
         }
 
         if (!hasTarget) {
@@ -681,6 +678,12 @@
             return false;
         }
 
+        // Ensure the final animation targets which hidden by transition could be visible.
+        for (int i = 0; i < targets.size(); i++) {
+            final WindowContainer wc = targets.get(i).mContainer;
+            wc.prepareSurfaces();
+        }
+
         scheduleAnimation(mPendingAnimationBuilder);
         mPendingAnimationBuilder = null;
         return true;
@@ -1080,7 +1083,7 @@
 
             boolean containTarget(@NonNull WindowContainer wc) {
                 return wc == mOpenTarget || wc == mCloseTarget
-                        || wc.hasChild(mOpenTarget) || wc.hasChild(mCloseTarget);
+                        || mOpenTarget.hasChild(wc) || mCloseTarget.hasChild(wc);
             }
 
             /**
@@ -1155,6 +1158,11 @@
     private static void setLaunchBehind(@NonNull ActivityRecord activity) {
         if (!activity.isVisibleRequested()) {
             activity.setVisibility(true);
+            // The transition could commit the visibility and in the finishing state, that could
+            // skip commitVisibility call in setVisibility cause the activity won't visible here.
+            // Call it again to make sure the activity could be visible while handling the pending
+            // animation.
+            activity.commitVisibility(true, true);
         }
         activity.mLaunchTaskBehind = true;
 
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 57fca3ae..2378469 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -146,8 +146,6 @@
 
     @VisibleForTesting
     boolean mIsAddingTaskToTargets;
-    @VisibleForTesting
-    boolean mShouldAttachNavBarToAppDuringTransition;
     private boolean mNavigationBarAttachedToApp;
     private ActivityRecord mNavBarAttachedApp;
 
@@ -379,8 +377,6 @@
         mDisplayId = displayId;
         mStatusBar = LocalServices.getService(StatusBarManagerInternal.class);
         mDisplayContent = service.mRoot.getDisplayContent(displayId);
-        mShouldAttachNavBarToAppDuringTransition =
-                mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition();
     }
 
     /**
@@ -577,7 +573,7 @@
     }
 
     private void attachNavigationBarToApp() {
-        if (!mShouldAttachNavBarToAppDuringTransition
+        if (!mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
                 // Skip the case where the nav bar is controlled by fade rotation.
                 || mDisplayContent.getAsyncRotationController() != null) {
             return;
@@ -652,7 +648,7 @@
     }
 
     void animateNavigationBarForAppLaunch(long duration) {
-        if (!mShouldAttachNavBarToAppDuringTransition
+        if (!mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
                 // Skip the case where the nav bar is controlled by fade rotation.
                 || mDisplayContent.getAsyncRotationController() != null
                 || mNavigationBarAttachedToApp
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 07daa4b..ad93454 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2335,9 +2335,7 @@
                     transition.playNow();
                 };
                 if (display.mTransitionController.isCollecting()) {
-                    mWmService.mSyncEngine.queueSyncSet(
-                            () -> display.mTransitionController.moveToCollecting(transition),
-                            sendSleepTransition);
+                    display.mTransitionController.queueCollecting(transition, sendSleepTransition);
                 } else {
                     display.mTransitionController.moveToCollecting(transition);
                     sendSleepTransition.run();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index fb592e1..89f9753 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5635,8 +5635,7 @@
             if (mWmService.mSyncEngine.hasActiveSync()) {
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                         "Creating Pending Move-to-back: %s", transition);
-                mWmService.mSyncEngine.queueSyncSet(
-                        () -> mTransitionController.moveToCollecting(transition),
+                mTransitionController.queueCollecting(transition,
                         () -> {
                             // Need to check again since this happens later and the system might
                             // be in a different state.
@@ -5684,6 +5683,7 @@
             // Usually resuming a top activity triggers the next app transition, but nothing's got
             // resumed in this case, so we need to execute it explicitly.
             mDisplayContent.executeAppTransition();
+            mDisplayContent.setFocusedApp(topActivity);
         } else {
             mRootWindowContainer.resumeFocusedTasksTopActivities();
         }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 3a909ce..652c297 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -287,7 +287,7 @@
 
         if (restoreBelow != null) {
             final Task transientRootTask = activity.getRootTask();
-            // Collect all visible activities which can be occluded by the transient activity to
+            // Collect all visible tasks which can be occluded by the transient activity to
             // make sure they are in the participants so their visibilities can be updated when
             // finishing transition.
             ((WindowContainer<?>) restoreBelow.getParent()).forAllTasks(t -> {
@@ -297,11 +297,7 @@
                         mTransientHideTasks.add(t);
                     }
                     if (t.isLeafTask()) {
-                        t.forAllActivities(r -> {
-                            if (r.isVisibleRequested()) {
-                                collect(r);
-                            }
-                        });
+                        collect(t);
                     }
                 }
                 return t == restoreBelow;
@@ -904,6 +900,18 @@
         mController.mFinishingTransition = this;
 
         if (mTransientHideTasks != null && !mTransientHideTasks.isEmpty()) {
+            // Record all the now-hiding activities so that they are committed after
+            // recalculating visibilities. We just use mParticipants because we can and it will
+            // ensure proper reporting of `isInFinishTransition`.
+            for (int i = 0; i < mTransientHideTasks.size(); ++i) {
+                mTransientHideTasks.get(i).forAllActivities(r -> {
+                    // Only check leaf-tasks that were collected
+                    if (!mParticipants.contains(r.getTask())) return;
+                    // Only concern ourselves with anything that can become invisible
+                    if (!r.isVisible()) return;
+                    mParticipants.add(r);
+                });
+            }
             // The transient hide tasks could be occluded now, e.g. returning to home. So trigger
             // the update to make the activities in the tasks invisible-requested, then the next
             // step can continue to commit the visibility.
@@ -953,7 +961,10 @@
                         enterAutoPip = true;
                     }
                 }
-                if (mChanges.get(ar).mVisible != visibleAtTransitionEnd) {
+                final ChangeInfo changeInfo = mChanges.get(ar);
+                // Due to transient-hide, there may be some activities here which weren't in the
+                // transition.
+                if (changeInfo != null && changeInfo.mVisible != visibleAtTransitionEnd) {
                     // Legacy dispatch relies on this (for now).
                     ar.mEnteringAnimation = visibleAtTransitionEnd;
                 } else if (mTransientLaunches != null && mTransientLaunches.containsKey(ar)
@@ -1609,7 +1620,7 @@
         sb.append(Integer.toHexString(System.identityHashCode(this)));
         sb.append(" id=" + mSyncId);
         sb.append(" type=" + transitTypeToString(mType));
-        sb.append(" flags=" + mFlags);
+        sb.append(" flags=0x" + Integer.toHexString(mFlags));
         sb.append('}');
         return sb.toString();
     }
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 3bf8969..4c1c2ff 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -443,26 +443,36 @@
         return type == TRANSIT_OPEN || type == TRANSIT_CLOSE;
     }
 
-    /** Whether the display change should run with blast sync. */
-    private static boolean shouldSync(@NonNull TransitionRequestInfo.DisplayChange displayChange) {
-        if ((displayChange.getStartRotation() + displayChange.getEndRotation()) % 2 == 0) {
+    /** Sets the sync method for the display change. */
+    private void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange,
+            @NonNull Transition displayTransition, @NonNull DisplayContent displayContent) {
+        final int startRotation = displayChange.getStartRotation();
+        final int endRotation = displayChange.getEndRotation();
+        if (startRotation != endRotation && (startRotation + endRotation) % 2 == 0) {
             // 180 degrees rotation change may not change screen size. So the clients may draw
             // some frames before and after the display projection transaction is applied by the
             // remote player. That may cause some buffers to show in different rotation. So use
             // sync method to pause clients drawing until the projection transaction is applied.
-            return true;
+            mAtm.mWindowManager.mSyncEngine.setSyncMethod(displayTransition.getSyncId(),
+                    BLASTSyncEngine.METHOD_BLAST);
         }
         final Rect startBounds = displayChange.getStartAbsBounds();
         final Rect endBounds = displayChange.getEndAbsBounds();
-        if (startBounds == null || endBounds == null) return false;
+        if (startBounds == null || endBounds == null) return;
         final int startWidth = startBounds.width();
         final int startHeight = startBounds.height();
         final int endWidth = endBounds.width();
         final int endHeight = endBounds.height();
         // This is changing screen resolution. Because the screen decor layers are excluded from
         // screenshot, their draw transactions need to run with the start transaction.
-        return (endWidth > startWidth) == (endHeight > startHeight)
-                && (endWidth != startWidth || endHeight != startHeight);
+        if ((endWidth > startWidth) == (endHeight > startHeight)
+                && (endWidth != startWidth || endHeight != startHeight)) {
+            displayContent.forAllWindows(w -> {
+                if (w.mToken.mRoundedCornerOverlay && w.mHasSurface) {
+                    w.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST;
+                }
+            }, true /* traverseTopToBottom */);
+        }
     }
 
     /**
@@ -494,9 +504,9 @@
         } else {
             newTransition = requestStartTransition(createTransition(type, flags),
                     trigger != null ? trigger.asTask() : null, remoteTransition, displayChange);
-            if (newTransition != null && displayChange != null && shouldSync(displayChange)) {
-                mAtm.mWindowManager.mSyncEngine.setSyncMethod(newTransition.getSyncId(),
-                        BLASTSyncEngine.METHOD_BLAST);
+            if (newTransition != null && displayChange != null && trigger != null
+                    && trigger.asDisplayContent() != null) {
+                setDisplaySyncMethod(displayChange, newTransition, trigger.asDisplayContent());
             }
         }
         if (trigger != null) {
@@ -874,6 +884,15 @@
         proto.end(token);
     }
 
+    void queueCollecting(Transition transit, Runnable onCollectStart) {
+        mAtm.mWindowManager.mSyncEngine.queueSyncSet(
+                // Make sure to collect immediately to prevent another transition
+                // from sneaking in before it. Note: moveToCollecting internally
+                // calls startSyncSet.
+                () -> moveToCollecting(transit),
+                onCollectStart);
+    }
+
     /**
      * This manages the animating state of processes that are running remote animations for
      * {@link #mTransitionPlayerProc}.
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 32d54d7..f63470f2 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -291,23 +291,21 @@
                     if (type < 0) {
                         throw new IllegalArgumentException("Can't create transition with no type");
                     }
+                    transition = new Transition(type, 0 /* flags */, mTransitionController,
+                            mService.mWindowManager.mSyncEngine);
                     // If there is already a collecting transition, queue up a new transition and
                     // return that. The actual start and apply will then be deferred until that
                     // transition starts collecting. This should almost never happen except during
                     // tests.
                     if (mService.mWindowManager.mSyncEngine.hasActiveSync()) {
                         Slog.w(TAG, "startTransition() while one is already collecting.");
-                        final Transition nextTransition = new Transition(type, 0 /* flags */,
-                                mTransitionController, mService.mWindowManager.mSyncEngine);
+                        final Transition nextTransition = transition;
                         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                                 "Creating Pending Transition: %s", nextTransition);
-                        mService.mWindowManager.mSyncEngine.queueSyncSet(
-                                // Make sure to collect immediately to prevent another transition
-                                // from sneaking in before it. Note: moveToCollecting internally
-                                // calls startSyncSet.
-                                () -> mTransitionController.moveToCollecting(nextTransition),
+                        mTransitionController.queueCollecting(nextTransition,
                                 () -> {
                                     nextTransition.start();
+                                    nextTransition.mLogger.mStartWCT = wct;
                                     applyTransaction(wct, -1 /*syncId*/, nextTransition, caller);
                                     if (needsSetReady) {
                                         nextTransition.setAllReady();
@@ -315,7 +313,7 @@
                                 });
                         return nextTransition.getToken();
                     }
-                    transition = mTransitionController.createTransition(type);
+                    mTransitionController.moveToCollecting(transition);
                 }
                 if (!transition.isCollecting() && !transition.isForcePlaying()) {
                     Slog.e(TAG, "Trying to start a transition that isn't collecting. This probably"
@@ -474,11 +472,7 @@
                     mTransitionController, mService.mWindowManager.mSyncEngine);
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                     "Creating Pending Transition for TaskFragment: %s", nextTransition);
-            mService.mWindowManager.mSyncEngine.queueSyncSet(
-                    // Make sure to collect immediately to prevent another transition
-                    // from sneaking in before it. Note: moveToCollecting internally
-                    // calls startSyncSet.
-                    () -> mTransitionController.moveToCollecting(nextTransition),
+            mTransitionController.queueCollecting(nextTransition,
                     () -> {
                         if (mTaskFragmentOrganizerController.isValidTransaction(wct)
                                 && (applyTransaction(wct, -1 /* syncId */, nextTransition, caller)
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index f111a95..926c7e4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -27,6 +27,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.BroadcastOptions;
 import android.app.admin.DevicePolicyIdentifiers;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyState;
@@ -353,6 +354,7 @@
                     policyDefinition,
                     userId);
         }
+        sendDevicePolicyChangedToSystem(userId);
     }
 
     /**
@@ -478,6 +480,8 @@
                 enforcingAdmin,
                 policyDefinition,
                 UserHandle.USER_ALL);
+
+        sendDevicePolicyChangedToSystem(UserHandle.USER_ALL);
     }
 
     /**
@@ -699,7 +703,7 @@
 
         if (policyDefinition.isGlobalOnlyPolicy()) {
             throw new IllegalArgumentException(policyDefinition.getPolicyKey() + " is a global only"
-                    + "policy.");
+                    + " policy.");
         }
 
         if (!mLocalPolicies.contains(userId)) {
@@ -724,7 +728,7 @@
     private <V> PolicyState<V> getGlobalPolicyStateLocked(PolicyDefinition<V> policyDefinition) {
         if (policyDefinition.isLocalOnlyPolicy()) {
             throw new IllegalArgumentException(policyDefinition.getPolicyKey() + " is a local only"
-                    + "policy.");
+                    + " policy.");
         }
 
         if (!mGlobalPolicies.containsKey(policyDefinition.getPolicyKey())) {
@@ -761,6 +765,20 @@
                 policyValue == null ? null : policyValue.getValue(), mContext, userId);
     }
 
+    private void sendDevicePolicyChangedToSystem(int userId) {
+        Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+        intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        Bundle options = new BroadcastOptions()
+                .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+                .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
+                .toBundle();
+        Binder.withCleanCallingIdentity(() -> mContext.sendBroadcastAsUser(
+                intent,
+                new UserHandle(userId),
+                /* receiverPermissions= */ null,
+                options));
+    }
+
     private <V> void sendPolicyResultToAdmin(
             EnforcingAdmin admin, PolicyDefinition<V> policyDefinition, int result, int userId) {
         Intent intent = new Intent(PolicyUpdateReceiver.ACTION_DEVICE_POLICY_SET_RESULT);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 94e6e73..29f9a30 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.BIND_DEVICE_ADMIN;
 import static android.Manifest.permission.LOCK_DEVICE;
 import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
+import static android.Manifest.permission.MANAGE_DEFAULT_APPLICATIONS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL;
@@ -59,6 +60,7 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RESET_PASSWORD;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SAFE_BOOT;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SCREEN_CAPTURE;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SCREEN_CONTENT;
@@ -441,7 +443,6 @@
 import android.util.DebugUtils;
 import android.util.IndentingPrintWriter;
 import android.util.IntArray;
-import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -7767,12 +7768,14 @@
                 // Explicit behaviour
                 if (factoryReset) {
                     // TODO(b/254031494) Replace with new factory reset permission checks
-                    boolean hasPermission = isDeviceOwnerUserId(userId)
-                            || (isOrganizationOwnedDeviceWithManagedProfile()
-                            && calledOnParentInstance);
-                    Preconditions.checkState(hasPermission,
-                            "Admin %s does not have permission to factory reset the device.",
-                            userId);
+                    if (!isPermissionCheckFlagEnabled()) {
+                        boolean hasPermission = isDeviceOwnerUserId(userId)
+                                || (isOrganizationOwnedDeviceWithManagedProfile()
+                                && calledOnParentInstance);
+                        Preconditions.checkCallAuthorization(hasPermission,
+                                "Admin %s does not have permission to factory reset the device.",
+                                userId);
+                    }
                     wipeDevice = true;
                 } else {
                     Preconditions.checkCallAuthorization(!isSystemUser,
@@ -13131,19 +13134,22 @@
         }
         int userId = caller.getUserId();
 
-        if (!UserRestrictionsUtils.isValidRestriction(key)) {
-            return;
-        }
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_USER_RESTRICTION);
 
         if (isPolicyEngineForFinanceFlagEnabled()) {
-            int affectedUserId = parent ? getProfileParentId(userId) : userId;
-            EnforcingAdmin admin = enforcePermissionForUserRestriction(
-                    who,
-                    key,
-                    caller.getPackageName(),
-                    affectedUserId);
-            if (mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE, callerPackage, userId)) {
+            if (!isDeviceOwner(caller) && !isProfileOwner(caller)) {
+                if (!mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE, callerPackage, userId)) {
+                    throw new IllegalStateException("Calling package is not targeting Android U.");
+                }
+                if (!UserRestrictionsUtils.isValidRestriction(key)) {
+                    throw new IllegalArgumentException("Invalid restriction key: " + key);
+                }
+                int affectedUserId = parent ? getProfileParentId(userId) : userId;
+                EnforcingAdmin admin = enforcePermissionForUserRestriction(
+                        who,
+                        key,
+                        caller.getPackageName(),
+                        affectedUserId);
                 PolicyDefinition<Boolean> policyDefinition =
                         PolicyDefinition.getPolicyDefinitionForUserRestriction(key);
                 if (enabledFromThisOwner) {
@@ -13155,7 +13161,8 @@
                         setGlobalUserRestrictionInternal(admin, key, /* enabled= */ false);
                     }
                     if (!policyDefinition.isGlobalOnlyPolicy()) {
-                        setLocalUserRestrictionInternal(admin, key, /* enabled= */ false, userId);
+                        setLocalUserRestrictionInternal(admin, key, /* enabled= */ false,
+                                userId);
 
                         int parentUserId = getProfileParentId(userId);
                         if (parentUserId != userId) {
@@ -13165,49 +13172,21 @@
                     }
                 }
             } else {
+                if (!UserRestrictionsUtils.isValidRestriction(key)) {
+                    return;
+                }
+                Objects.requireNonNull(who, "ComponentName is null");
+                EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackage);
+                checkAdminCanSetRestriction(caller, parent, key);
                 setBackwardCompatibleUserRestriction(
                         caller, admin, key, enabledFromThisOwner, parent);
             }
         } else {
+            if (!UserRestrictionsUtils.isValidRestriction(key)) {
+                return;
+            }
             Objects.requireNonNull(who, "ComponentName is null");
-            if (parent) {
-                Preconditions.checkCallAuthorization(
-                        isProfileOwnerOfOrganizationOwnedDevice(caller));
-            } else {
-                Preconditions.checkCallAuthorization(
-                        isDeviceOwner(caller) || isProfileOwner(caller));
-            }
-            synchronized (getLockObject()) {
-                if (isDefaultDeviceOwner(caller)) {
-                    if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
-                        throw new SecurityException("Device owner cannot set user restriction "
-                                + key);
-                    }
-                    Preconditions.checkArgument(!parent,
-                            "Cannot use the parent instance in Device Owner mode");
-                } else if (isFinancedDeviceOwner(caller)) {
-                    if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(key)) {
-                        throw new SecurityException("Cannot set user restriction " + key
-                                + " when managing a financed device");
-                    }
-                    Preconditions.checkArgument(!parent,
-                            "Cannot use the parent instance in Financed Device Owner"
-                                    + " mode");
-                } else {
-                    boolean profileOwnerCanChangeOnItself = !parent
-                            && UserRestrictionsUtils.canProfileOwnerChange(
-                            key, userId == getMainUserId());
-                    boolean orgOwnedProfileOwnerCanChangeGlobally = parent
-                            && isProfileOwnerOfOrganizationOwnedDevice(caller)
-                            && UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange(
-                            key);
-
-                    if (!profileOwnerCanChangeOnItself && !orgOwnedProfileOwnerCanChangeGlobally) {
-                        throw new SecurityException("Profile owner cannot set user restriction "
-                                + key);
-                    }
-                }
-            }
+            checkAdminCanSetRestriction(caller, parent, key);
             synchronized (getLockObject()) {
                 final ActiveAdmin activeAdmin = getParentOfAdminIfRequired(
                         getProfileOwnerOrDeviceOwnerLocked(userId), parent);
@@ -13224,6 +13203,46 @@
         logUserRestrictionCall(key, enabledFromThisOwner, parent, caller);
     }
 
+    private void checkAdminCanSetRestriction(CallerIdentity caller, boolean parent, String key) {
+        if (parent) {
+            Preconditions.checkCallAuthorization(
+                    isProfileOwnerOfOrganizationOwnedDevice(caller));
+        } else {
+            Preconditions.checkCallAuthorization(
+                    isDeviceOwner(caller) || isProfileOwner(caller));
+        }
+        synchronized (getLockObject()) {
+            if (isDefaultDeviceOwner(caller)) {
+                if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
+                    throw new SecurityException("Device owner cannot set user restriction "
+                            + key);
+                }
+                Preconditions.checkArgument(!parent,
+                        "Cannot use the parent instance in Device Owner mode");
+            } else if (isFinancedDeviceOwner(caller)) {
+                if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(key)) {
+                    throw new SecurityException("Cannot set user restriction " + key
+                            + " when managing a financed device");
+                }
+                Preconditions.checkArgument(!parent,
+                        "Cannot use the parent instance in Financed Device Owner"
+                                + " mode");
+            } else {
+                boolean profileOwnerCanChangeOnItself = !parent
+                        && UserRestrictionsUtils.canProfileOwnerChange(
+                        key, caller.getUserId() == getMainUserId());
+                boolean orgOwnedProfileOwnerCanChangeGlobally = parent
+                        && isProfileOwnerOfOrganizationOwnedDevice(caller)
+                        && UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange(
+                        key);
+
+                if (!profileOwnerCanChangeOnItself && !orgOwnedProfileOwnerCanChangeGlobally) {
+                    throw new SecurityException("Profile owner cannot set user restriction "
+                            + key);
+                }
+            }
+        }
+    }
     private void setBackwardCompatibleUserRestriction(
             CallerIdentity caller, EnforcingAdmin admin, String key, boolean enabled,
             boolean parent) {
@@ -13252,20 +13271,22 @@
     @Override
     public void setUserRestrictionGlobally(String callerPackage, String key) {
         final CallerIdentity caller = getCallerIdentity(callerPackage);
-        if (!UserRestrictionsUtils.isValidRestriction(key)) {
-            return;
-        }
 
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_USER_RESTRICTION);
 
         if (!isPolicyEngineForFinanceFlagEnabled()) {
             throw new IllegalStateException("Feature flag is not enabled.");
         }
-
+        if (isDeviceOwner(caller) || isProfileOwner(caller)) {
+            throw new IllegalStateException("Admins are not allowed to call this API.");
+        }
         if (!mInjector.isChangeEnabled(
                 ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) {
             throw new IllegalStateException("Calling package is not targeting Android U.");
         }
+        if (!UserRestrictionsUtils.isValidRestriction(key)) {
+            throw new IllegalArgumentException("Invalid restriction key: " + key);
+        }
 
         EnforcingAdmin admin = enforcePermissionForUserRestriction(
                 /* who= */ null,
@@ -13416,14 +13437,25 @@
             int targetUserId = parent
                     ? getProfileParentId(caller.getUserId()) : caller.getUserId();
             EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackage);
-            Bundle restrictions = getUserRestrictionsFromPolicyEngine(admin, targetUserId);
-            // Add global restrictions set by the admin as well if admin is not targeting Android U.
-            if (!mInjector.isChangeEnabled(
-                    ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) {
+            if (isDeviceOwner(caller) || isProfileOwner(caller)) {
+                Objects.requireNonNull(who, "ComponentName is null");
+                Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+                        || isFinancedDeviceOwner(caller)
+                        || isProfileOwner(caller)
+                        || (parent && isProfileOwnerOfOrganizationOwnedDevice(caller)));
+
+                Bundle restrictions = getUserRestrictionsFromPolicyEngine(admin, targetUserId);
+                // Add global restrictions set by the admin as well.
                 restrictions.putAll(
                         getUserRestrictionsFromPolicyEngine(admin, UserHandle.USER_ALL));
+                return restrictions;
+            } else {
+                if (!mInjector.isChangeEnabled(
+                        ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) {
+                    throw new IllegalStateException("Calling package is not targeting Android U.");
+                }
+                return getUserRestrictionsFromPolicyEngine(admin, targetUserId);
             }
-            return restrictions;
         } else {
             Objects.requireNonNull(who, "ComponentName is null");
             Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
@@ -13439,164 +13471,162 @@
     }
 
     // Map of user restriction to permission.
-    private static final HashMap<String, String> USER_RESTRICTION_PERMISSIONS = new HashMap<>();
+    private static final HashMap<String, String[]> USER_RESTRICTION_PERMISSIONS = new HashMap<>();
     {
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.ENSURE_VERIFY_APPS, MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES);
+                UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, new String[]{MANAGE_DEVICE_POLICY_PROFILES});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_WIFI_TETHERING, MANAGE_DEVICE_POLICY_WIFI);
+                UserManager.DISALLOW_ADD_CLONE_PROFILE, new String[]{MANAGE_DEVICE_POLICY_PROFILES});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_WIFI_DIRECT, MANAGE_DEVICE_POLICY_WIFI);
+                UserManager.DISALLOW_ADD_USER, new String[]{MANAGE_DEVICE_POLICY_USERS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_USER_SWITCH, MANAGE_DEVICE_POLICY_USERS);
+                UserManager.DISALLOW_ADD_WIFI_CONFIG, new String[]{MANAGE_DEVICE_POLICY_WIFI});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_USB_FILE_TRANSFER, MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER);
+                UserManager.DISALLOW_ADJUST_VOLUME, new String[]{MANAGE_DEVICE_POLICY_AUDIO_OUTPUT});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_UNMUTE_MICROPHONE, MANAGE_DEVICE_POLICY_MICROPHONE);
+                UserManager.DISALLOW_AIRPLANE_MODE, new String[]{MANAGE_DEVICE_POLICY_AIRPLANE_MODE});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_UNMUTE_DEVICE, MANAGE_DEVICE_POLICY_AUDIO_OUTPUT);
+                UserManager.DISALLOW_AMBIENT_DISPLAY, new String[]{MANAGE_DEVICE_POLICY_DISPLAY});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_UNINSTALL_APPS, MANAGE_DEVICE_POLICY_APPS_CONTROL);
+                UserManager.DISALLOW_APPS_CONTROL, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_UNIFIED_PASSWORD, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS);
+                UserManager.DISALLOW_AUTOFILL, new String[]{MANAGE_DEVICE_POLICY_AUTOFILL});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS);
+                UserManager.DISALLOW_BLUETOOTH, new String[]{MANAGE_DEVICE_POLICY_BLUETOOTH});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SMS, MANAGE_DEVICE_POLICY_SMS);
+                UserManager.DISALLOW_BLUETOOTH_SHARING, new String[]{MANAGE_DEVICE_POLICY_BLUETOOTH});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, MANAGE_DEVICE_POLICY_WIFI);
+                UserManager.DISALLOW_CAMERA, new String[]{MANAGE_DEVICE_POLICY_CAMERA});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SHARE_LOCATION, MANAGE_DEVICE_POLICY_LOCATION);
+                UserManager.DISALLOW_CAMERA_TOGGLE, new String[]{MANAGE_DEVICE_POLICY_CAMERA_TOGGLE});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE,
-                MANAGE_DEVICE_POLICY_PROFILE_INTERACTION);
+                UserManager.DISALLOW_CELLULAR_2G, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SET_WALLPAPER, MANAGE_DEVICE_POLICY_WALLPAPER);
+                UserManager.DISALLOW_CHANGE_WIFI_STATE, new String[]{MANAGE_DEVICE_POLICY_WIFI});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SET_USER_ICON, MANAGE_DEVICE_POLICY_USERS);
+                UserManager.DISALLOW_CONFIG_BLUETOOTH, new String[]{MANAGE_DEVICE_POLICY_BLUETOOTH});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SAFE_BOOT, MANAGE_DEVICE_POLICY_SAFE_BOOT);
+                UserManager.DISALLOW_CONFIG_BRIGHTNESS, new String[]{MANAGE_DEVICE_POLICY_DISPLAY});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_RUN_IN_BACKGROUND, MANAGE_DEVICE_POLICY_SAFE_BOOT);
+                UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_REMOVE_USER, MANAGE_DEVICE_POLICY_USERS);
+                UserManager.DISALLOW_CONFIG_CREDENTIALS, new String[]{MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_PRINTING, MANAGE_DEVICE_POLICY_PRINTING);
+                UserManager.DISALLOW_CONFIG_DATE_TIME, new String[]{MANAGE_DEVICE_POLICY_TIME});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_OUTGOING_CALLS, MANAGE_DEVICE_POLICY_CALLS);
+                UserManager.DISALLOW_CONFIG_DEFAULT_APPS, new String[]{MANAGE_DEFAULT_APPLICATIONS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_OUTGOING_BEAM, MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION);
+                UserManager.DISALLOW_CONFIG_LOCALE, new String[]{MANAGE_DEVICE_POLICY_LOCALE});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_NETWORK_RESET, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+                UserManager.DISALLOW_CONFIG_LOCATION, new String[]{MANAGE_DEVICE_POLICY_LOCATION});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA);
+                UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_MODIFY_ACCOUNTS, MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT);
+                UserManager.DISALLOW_CONFIG_PRIVATE_DNS, new String[]{MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_MICROPHONE_TOGGLE, MANAGE_DEVICE_POLICY_MICROPHONE);
+                UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT, new String[]{MANAGE_DEVICE_POLICY_DISPLAY});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY,
-                MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES);
+                UserManager.DISALLOW_CONFIG_TETHERING, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
-                MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES);
+                UserManager.DISALLOW_CONFIG_VPN, new String[]{MANAGE_DEVICE_POLICY_VPN});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_INSTALL_APPS, MANAGE_DEVICE_POLICY_APPS_CONTROL);
+                UserManager.DISALLOW_CONFIG_WIFI, new String[]{MANAGE_DEVICE_POLICY_WIFI});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_FUN, MANAGE_DEVICE_POLICY_FUN);
+                UserManager.DISALLOW_CONTENT_CAPTURE, new String[]{MANAGE_DEVICE_POLICY_SCREEN_CONTENT});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_FACTORY_RESET, MANAGE_DEVICE_POLICY_FACTORY_RESET);
+                UserManager.DISALLOW_CONTENT_SUGGESTIONS, new String[]{MANAGE_DEVICE_POLICY_SCREEN_CONTENT});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_DEBUGGING_FEATURES, MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES);
+                UserManager.DISALLOW_CREATE_WINDOWS, new String[]{MANAGE_DEVICE_POLICY_WINDOWS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_DATA_ROAMING, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+                UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE, new String[]{MANAGE_DEVICE_POLICY_PROFILE_INTERACTION});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE,
-                MANAGE_DEVICE_POLICY_PROFILE_INTERACTION);
+                UserManager.DISALLOW_DATA_ROAMING, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CREATE_WINDOWS, MANAGE_DEVICE_POLICY_WINDOWS);
+                UserManager.DISALLOW_DEBUGGING_FEATURES, new String[]{MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONTENT_SUGGESTIONS, MANAGE_DEVICE_POLICY_SCREEN_CONTENT);
+                UserManager.DISALLOW_FACTORY_RESET, new String[]{MANAGE_DEVICE_POLICY_FACTORY_RESET});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONTENT_CAPTURE, MANAGE_DEVICE_POLICY_SCREEN_CONTENT);
+                UserManager.DISALLOW_FUN, new String[]{MANAGE_DEVICE_POLICY_FUN});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_WIFI, MANAGE_DEVICE_POLICY_WIFI);
+                UserManager.DISALLOW_INSTALL_APPS, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_VPN, MANAGE_DEVICE_POLICY_VPN);
+                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, new String[]{MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_TETHERING, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, new String[]{MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT, MANAGE_DEVICE_POLICY_DISPLAY);
+                UserManager.DISALLOW_MICROPHONE_TOGGLE, new String[]{MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_PRIVATE_DNS, MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS);
+                UserManager.DISALLOW_MODIFY_ACCOUNTS, new String[]{MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+                UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, new String[]{MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_LOCATION, MANAGE_DEVICE_POLICY_LOCATION);
+                UserManager.DISALLOW_NETWORK_RESET, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_LOCALE, MANAGE_DEVICE_POLICY_LOCALE);
+                UserManager.DISALLOW_OUTGOING_BEAM, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_DATE_TIME, MANAGE_DEVICE_POLICY_TIME);
+                UserManager.DISALLOW_OUTGOING_CALLS, new String[]{MANAGE_DEVICE_POLICY_CALLS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_CREDENTIALS, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS);
+                UserManager.DISALLOW_PRINTING, new String[]{MANAGE_DEVICE_POLICY_PRINTING});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+                UserManager.DISALLOW_REMOVE_USER, new String[]{MANAGE_DEVICE_POLICY_USERS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_BRIGHTNESS, MANAGE_DEVICE_POLICY_DISPLAY);
+                UserManager.DISALLOW_RUN_IN_BACKGROUND, new String[]{MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_BLUETOOTH, MANAGE_DEVICE_POLICY_BLUETOOTH);
+                UserManager.DISALLOW_SAFE_BOOT, new String[]{MANAGE_DEVICE_POLICY_SAFE_BOOT});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CHANGE_WIFI_STATE, MANAGE_DEVICE_POLICY_WIFI);
+                UserManager.DISALLOW_SET_USER_ICON, new String[]{MANAGE_DEVICE_POLICY_USERS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CAMERA_TOGGLE, MANAGE_DEVICE_POLICY_CAMERA);
+                UserManager.DISALLOW_SET_WALLPAPER, new String[]{MANAGE_DEVICE_POLICY_WALLPAPER});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CAMERA, MANAGE_DEVICE_POLICY_CAMERA);
+                UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, new String[]{MANAGE_DEVICE_POLICY_PROFILE_INTERACTION});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_BLUETOOTH_SHARING, MANAGE_DEVICE_POLICY_BLUETOOTH);
+                UserManager.DISALLOW_SHARE_LOCATION, new String[]{MANAGE_DEVICE_POLICY_LOCATION});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_BLUETOOTH, MANAGE_DEVICE_POLICY_BLUETOOTH);
+                UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, new String[]{MANAGE_DEVICE_POLICY_WIFI});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_BIOMETRIC, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS);
+                UserManager.DISALLOW_SMS, new String[]{MANAGE_DEVICE_POLICY_SMS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_AUTOFILL, MANAGE_DEVICE_POLICY_AUTOFILL);
+                UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, new String[]{MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_APPS_CONTROL, MANAGE_DEVICE_POLICY_APPS_CONTROL);
+                UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_AMBIENT_DISPLAY, MANAGE_DEVICE_POLICY_DISPLAY);
+                UserManager.DISALLOW_UNIFIED_PASSWORD, new String[]{MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_AIRPLANE_MODE, MANAGE_DEVICE_POLICY_AIRPLANE_MODE);
+                UserManager.DISALLOW_UNINSTALL_APPS, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_ADJUST_VOLUME, MANAGE_DEVICE_POLICY_AUDIO_OUTPUT);
+                UserManager.DISALLOW_UNMUTE_DEVICE, new String[]{MANAGE_DEVICE_POLICY_AUDIO_OUTPUT});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_ADD_WIFI_CONFIG, MANAGE_DEVICE_POLICY_WIFI);
+                UserManager.DISALLOW_UNMUTE_MICROPHONE, new String[]{MANAGE_DEVICE_POLICY_MICROPHONE});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_ADD_USER, MANAGE_DEVICE_POLICY_USERS);
+                UserManager.DISALLOW_USB_FILE_TRANSFER, new String[]{MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_ADD_CLONE_PROFILE, MANAGE_DEVICE_POLICY_PROFILES);
+                UserManager.DISALLOW_USER_SWITCH, new String[]{MANAGE_DEVICE_POLICY_USERS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, MANAGE_DEVICE_POLICY_PROFILES);
+                UserManager.DISALLOW_WIFI_DIRECT, new String[]{MANAGE_DEVICE_POLICY_WIFI});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CELLULAR_2G, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+                UserManager.DISALLOW_WIFI_TETHERING, new String[]{MANAGE_DEVICE_POLICY_WIFI});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO,
-                MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION);
+                UserManager.ENSURE_VERIFY_APPS, new String[]{MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES});
 
         // Restrictions not allowed to be set by admins.
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_RECORD_AUDIO, null);
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_WALLPAPER, null);
-        USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_DEFAULT_APPS, null);
     }
 
     private EnforcingAdmin enforcePermissionForUserRestriction(ComponentName who,
             String userRestriction, String callerPackageName, int userId) {
-        String permission = USER_RESTRICTION_PERMISSIONS.get(userRestriction);
-        if (permission != null) {
-            return enforcePermissionAndGetEnforcingAdmin(who, permission, callerPackageName,
-                    userId);
-        }
+        String[] permissions = USER_RESTRICTION_PERMISSIONS.get(userRestriction);
+        if (permissions.length > 0) {
+            try {
+                return enforcePermissionsAndGetEnforcingAdmin(who, permissions, callerPackageName,
+                        userId);
+            } catch (SecurityException e) {
+                throw new SecurityException("Caller does not hold the required permission for this "
+                        + "user restriction: " + userRestriction + ".\n" + e.getMessage());
+            }
+         }
         throw new SecurityException("Admins are not permitted to set User Restriction: "
                 + userRestriction);
     }
@@ -14017,9 +14047,12 @@
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
 
         if (isPolicyEngineForFinanceFlagEnabled()) {
-            EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+            EnforcingAdmin enforcingAdmin = enforcePermissionsAndGetEnforcingAdmin(
                     who,
-                    MANAGE_DEVICE_POLICY_APPS_CONTROL,
+                    new String[]{
+                            MANAGE_DEVICE_POLICY_APPS_CONTROL,
+                            MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL
+                    },
                     caller.getPackageName(),
                     caller.getUserId());
             mDevicePolicyEngine.setLocalPolicy(
@@ -16626,10 +16659,6 @@
         SENSOR_PERMISSIONS.add(Manifest.permission.BACKGROUND_CAMERA);
         SENSOR_PERMISSIONS.add(Manifest.permission.RECORD_BACKGROUND_AUDIO);
         SENSOR_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
-        SENSOR_PERMISSIONS.add(
-                Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE);
-        SENSOR_PERMISSIONS.add(
-                    Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND);
     }
 
     private boolean canGrantPermission(CallerIdentity caller, String permission,
@@ -22422,6 +22451,14 @@
         });
     }
 
+    // Permission that will need to be created in V.
+    private static final String MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL =
+            "manage_device_policy_block_uninstall";
+    private static final String MANAGE_DEVICE_POLICY_CAMERA_TOGGLE =
+            "manage_device_policy_camera_toggle";
+    private static final String MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE =
+            "manage_device_policy_microphone_toggle";
+
     // DPC types
     private static final int DEFAULT_DEVICE_OWNER = 0;
     private static final int FINANCED_DEVICE_OWNER = 1;
@@ -22645,7 +22682,7 @@
     {
         DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, DELEGATION_PERMISSION_GRANT);
         DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_APP_RESTRICTIONS, DELEGATION_APP_RESTRICTIONS);
-        DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_APPS_CONTROL, DELEGATION_BLOCK_UNINSTALL);
+        DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL, DELEGATION_BLOCK_UNINSTALL);
         DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, DELEGATION_SECURITY_LOGGING);
         DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_PACKAGE_STATE, DELEGATION_PACKAGE_ACCESS);
     }
@@ -22724,6 +22761,10 @@
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_AUTOFILL,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+        CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL,
+                MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+        CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_CAMERA_TOGGLE,
+                MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES,
@@ -22740,6 +22781,8 @@
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_TASK,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+        CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE,
+                MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PROFILES,
@@ -22770,13 +22813,34 @@
 
     /**
      * Checks if the calling process has been granted permission to apply a device policy on a
+     * specific user. Only one permission provided in the list needs to be granted to pass this
+     * check.
+     * The given permissions will be checked along with their associated cross-user permissions if
+     * they exists and the target user is different to the calling user.
+     * Returns an {@link EnforcingAdmin} for the caller.
+     *
+     * @param admin the component name of the admin.
+     * @param callerPackageName The package name of the calling application.
+     * @param permissions an array of permission names to be checked.
+     * @param targetUserId The userId of the user which the caller needs permission to act on.
+     * @throws SecurityException if the caller has not been granted the given permission,
+     * the associated cross-user permission if the caller's user is different to the target user.
+     */
+    private EnforcingAdmin enforcePermissionsAndGetEnforcingAdmin(@Nullable ComponentName admin,
+            String[] permissions, String callerPackageName, int targetUserId) {
+        enforcePermissions(permissions, callerPackageName, targetUserId);
+        return getEnforcingAdminForCaller(admin, callerPackageName);
+    }
+
+    /**
+     * Checks if the calling process has been granted permission to apply a device policy on a
      * specific user.
      * The given permission will be checked along with its associated cross-user permission if it
      * exists and the target user is different to the calling user.
      * Returns an {@link EnforcingAdmin} for the caller.
      *
      * @param admin the component name of the admin.
-     * @param callerPackageName The package name  of the calling application.
+     * @param callerPackageName The package name of the calling application.
      * @param permission The name of the permission being checked.
      * @param targetUserId The userId of the user which the caller needs permission to act on.
      * @throws SecurityException if the caller has not been granted the given permission,
@@ -22834,32 +22898,24 @@
             new HashMap<>();
 
     /**
-     * Checks if the calling process has been granted permission to apply a device policy on a
-     * specific user.
-     * The given permission will be checked along with its associated cross-user permission if it
-     * exists and the target user is different to the calling user.
+     * Checks if the calling process has been granted permission to apply a device policy.
      *
      * @param callerPackageName The package name  of the calling application.
      * @param permission The name of the permission being checked.
-     * @param targetUserId The userId of the user which the caller needs permission to act on.
      * @throws SecurityException if the caller has not been granted the given permission,
      * the associated cross-user permission if the caller's user is different to the target user.
      */
-    private void enforcePermission(String permission, String callerPackageName, int targetUserId)
+    private void enforcePermission(String permission, String callerPackageName)
             throws SecurityException {
-        if (!hasPermission(permission, callerPackageName, targetUserId)) {
-            // TODO(b/276920002): Split the error messages so that the cross-user permission
-            // is only mentioned when it is needed.
+        if (!hasPermission(permission, callerPackageName)) {
             throw new SecurityException("Caller does not have the required permissions for "
-                    + "this user. Permissions required: {"
+                    + "this user. Permission required: "
                     + permission
-                    + ", "
-                    + CROSS_USER_PERMISSIONS.get(permission)
-                    + "(if calling cross-user)"
-                    + "}");
+                    + ".");
         }
     }
 
+
     /**
      * Checks if the calling process has been granted permission to apply a device policy on a
      * specific user.
@@ -22872,23 +22928,66 @@
      * @throws SecurityException if the caller has not been granted the given permission,
      * the associated cross-user permission if the caller's user is different to the target user.
      */
-    private void enforcePermission(String permission, int adminPolicy,
-            String callerPackageName, int targetUserId)
+    private void enforcePermission(String permission, String callerPackageName, int targetUserId)
             throws SecurityException {
-        if (!hasPermissionOrAdminPolicy(permission, callerPackageName, adminPolicy, targetUserId)) {
-            // TODO(b/276920002): Split the error messages so that the cross-user permission
-            // is only mentioned when it is needed.
-            throw new SecurityException("Caller does not have the required permissions for "
-                    + "this user. Permissions required: {"
-                    + permission
-                    + ", "
-                    + CROSS_USER_PERMISSIONS.get(permission)
-                    + "(if calling cross-user)"
-                    + "}");
+        enforcePermission(permission, callerPackageName);
+        if (targetUserId != getCallerIdentity(callerPackageName).getUserId()) {
+            enforcePermission(CROSS_USER_PERMISSIONS.get(permission), callerPackageName);
         }
     }
 
     /**
+     * Checks if the calling process has been granted permission to apply a device policy on a
+     * specific user. Only one of the given permissions will be required to be held to pass this
+     * check.
+     * The given permissions will be checked along with their associated cross-user permissions if
+     * they exist and the target user is different to the calling user.
+     *
+     * @param permissions An array of the names of the permissions being checked.
+     * @param callerPackageName The package name  of the calling application.
+     * @param targetUserId The userId of the user which the caller needs permission to act on.
+     * @throws SecurityException if the caller has not been granted the given permission,
+     * the associated cross-user permission if the caller's user is different to the target user.
+     */
+    private void enforcePermissions(String[] permissions, String callerPackageName,
+            int targetUserId) throws SecurityException {
+        String heldPermission = "";
+        for (String permission : permissions) {
+            if (hasPermission(permission, callerPackageName)) {
+                heldPermission = permission;
+                break;
+            }
+        }
+        if (heldPermission.isEmpty()) {
+            throw new SecurityException("Caller does not have the required permissions for "
+                    + "this user. One of the following permission required: "
+                    + Arrays.toString(permissions));
+        }
+        enforcePermission(heldPermission, callerPackageName, targetUserId);
+    }
+
+    /**
+     * Checks if the calling process has been granted permission to apply a device policy on a
+     * specific user.
+     * The given permission will be checked along with its associated cross-user permission if it
+     * exists and the target user is different to the calling user.
+     *
+     * @param callerPackageName The package name  of the calling application.
+     * @param adminPolicy The admin policy that should grant holders permission.
+     * @param permission The name of the permission being checked.
+     * @param targetUserId The userId of the user which the caller needs permission to act on.
+     * @throws SecurityException if the caller has not been granted the given permission,
+     * the associated cross-user permission if the caller's user is different to the target user.
+     */
+    private void enforcePermission(String permission, int adminPolicy,
+            String callerPackageName, int targetUserId) throws SecurityException {
+        if (hasAdminPolicy(adminPolicy, callerPackageName)) {
+            return;
+        }
+        enforcePermission(permission, callerPackageName, targetUserId);
+    }
+
+    /**
      * Checks whether the calling process has been granted permission to query a device policy on
      * a specific user.
      * The given permission will be checked along with its associated cross-user permission if it
@@ -22909,6 +23008,12 @@
         enforcePermission(permission, callerPackageName, targetUserId);
     }
 
+    private boolean hasAdminPolicy(int adminPolicy, String callerPackageName) {
+        CallerIdentity caller = getCallerIdentity(callerPackageName);
+        ActiveAdmin deviceAdmin = getActiveAdminForCaller(null, caller);
+        return deviceAdmin != null && deviceAdmin.info.usesPolicy(adminPolicy);
+    }
+
     /**
      * Return whether the calling process has been granted permission to apply a device policy on
      * a specific user.
@@ -22921,24 +23026,15 @@
         CallerIdentity caller = getCallerIdentity(callerPackageName);
         boolean hasPermissionOnOwnUser = hasPermission(permission, caller.getPackageName());
         boolean hasPermissionOnTargetUser = true;
-        if (hasPermissionOnOwnUser & caller.getUserId() != targetUserId) {
-            hasPermissionOnTargetUser = hasPermission(CROSS_USER_PERMISSIONS.get(permission),
-                    caller.getPackageName());
+        if (hasPermissionOnOwnUser && caller.getUserId() != targetUserId) {
+            hasPermissionOnTargetUser = hasPermissionOnTargetUser
+                    && hasPermission(CROSS_USER_PERMISSIONS.get(permission),
+                        caller.getPackageName());
         }
 
         return hasPermissionOnOwnUser && hasPermissionOnTargetUser;
     }
 
-    private boolean hasPermissionOrAdminPolicy(String permission, String callerPackageName,
-            int adminPolicy, int targetUserId) {
-        CallerIdentity caller = getCallerIdentity(callerPackageName);
-        if (hasPermission(permission, caller.getPackageName(), targetUserId)) {
-            return true;
-        }
-        ActiveAdmin deviceAdmin = getActiveAdminForCaller(null, caller);
-        return deviceAdmin != null && deviceAdmin.info.usesPolicy(adminPolicy);
-    }
-
     /**
      * Return whether the calling process has been granted the given permission.
      *
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 509a66b..8c2468a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -284,6 +284,7 @@
     private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>();
     private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>();
 
+    // TODO(b/277218360): Revisit policies that should be marked as global-only.
     static {
         POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE);
         POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PERMISSION_GRANT_POLICY,
@@ -312,8 +313,9 @@
         USER_RESTRICTION_FLAGS.put(
                 UserManager.DISALLOW_WIFI_TETHERING, POLICY_FLAG_GLOBAL_ONLY_POLICY);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_GRANT_ADMIN, /* flags= */ 0);
+        // TODO: set as global only once we get rid of the mapping
         USER_RESTRICTION_FLAGS.put(
-                UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+                UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(
                 UserManager.DISALLOW_WIFI_DIRECT, POLICY_FLAG_GLOBAL_ONLY_POLICY);
         USER_RESTRICTION_FLAGS.put(
@@ -333,8 +335,10 @@
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_BLUETOOTH, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_BLUETOOTH, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_BLUETOOTH_SHARING, /* flags= */ 0);
+        // This effectively always applies globally, but it can be set on the profile
+        // parent, check the javadocs on the restriction for more info.
         USER_RESTRICTION_FLAGS.put(
-                UserManager.DISALLOW_USB_FILE_TRANSFER, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+                UserManager.DISALLOW_USB_FILE_TRANSFER, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_CREDENTIALS, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_REMOVE_USER, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, /* flags= */ 0);
@@ -344,8 +348,10 @@
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_DATE_TIME, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(
                 UserManager.DISALLOW_CONFIG_TETHERING, /* flags= */ 0);
+        // This effectively always applies globally, but it can be set on the profile
+        // parent, check the javadocs on the restriction for more info.
         USER_RESTRICTION_FLAGS.put(
-                UserManager.DISALLOW_NETWORK_RESET, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+                UserManager.DISALLOW_NETWORK_RESET, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_FACTORY_RESET, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ADD_USER, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ADD_MANAGED_PROFILE, /* flags= */ 0);
@@ -376,8 +382,7 @@
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_UNMUTE_DEVICE, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_DATA_ROAMING, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_SET_USER_ICON, /* flags= */ 0);
-            // TODO: double check flags
-        USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_OEM_UNLOCK, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+        USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_OEM_UNLOCK, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_UNIFIED_PASSWORD, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_AUTOFILL, /* flags= */ 0);
@@ -390,6 +395,7 @@
         USER_RESTRICTION_FLAGS.put(
                 UserManager.DISALLOW_CONFIG_PRIVATE_DNS, POLICY_FLAG_GLOBAL_ONLY_POLICY);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MICROPHONE_TOGGLE, /* flags= */ 0);
+        // TODO: According the UserRestrictionsUtils, this is global only, need to confirm.
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CAMERA_TOGGLE, /* flags= */ 0);
         // TODO: check if its global only
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_BIOMETRIC, /* flags= */ 0);
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 1a75170..7b771af 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -567,36 +567,36 @@
 
         // Ensure that no action is taken for cases where the failure reason is unknown
         assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN, 1),
-                PackageHealthObserverImpact.USER_IMPACT_NONE);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
 
         // Ensure the correct user impact is returned for each mitigation count.
         assertEquals(observer.onHealthCheckFailed(null,
                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
-                PackageHealthObserverImpact.USER_IMPACT_LOW);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
 
         assertEquals(observer.onHealthCheckFailed(null,
                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2),
-                PackageHealthObserverImpact.USER_IMPACT_LOW);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
 
         assertEquals(observer.onHealthCheckFailed(null,
                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3),
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
 
         assertEquals(observer.onHealthCheckFailed(null,
                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4),
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
     }
 
     @Test
     public void testBootLoopLevels() {
         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
 
-        assertEquals(observer.onBootLoop(0), PackageHealthObserverImpact.USER_IMPACT_NONE);
-        assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LOW);
-        assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LOW);
-        assertEquals(observer.onBootLoop(3), PackageHealthObserverImpact.USER_IMPACT_HIGH);
-        assertEquals(observer.onBootLoop(4), PackageHealthObserverImpact.USER_IMPACT_HIGH);
-        assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_HIGH);
+        assertEquals(observer.onBootLoop(0), PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
+        assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
+        assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
+        assertEquals(observer.onBootLoop(3), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
+        assertEquals(observer.onBootLoop(4), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
+        assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index ec177c9..bc3f5ab 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -237,9 +237,23 @@
     }
 
     private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
+            BroadcastRecord record, int recordIndex) {
+        enqueueOrReplaceBroadcast(queue, record, recordIndex, false, 42_000_000L);
+    }
+
+    private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
             BroadcastRecord record, int recordIndex, long enqueueTime) {
-        queue.enqueueOrReplaceBroadcast(record, recordIndex, false);
+        enqueueOrReplaceBroadcast(queue, record, recordIndex, false, enqueueTime);
+    }
+
+    private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
+            BroadcastRecord record, int recordIndex, boolean wouldBeSkipped, long enqueueTime) {
+        queue.enqueueOrReplaceBroadcast(record, recordIndex, wouldBeSkipped, (r, i) -> {
+            throw new UnsupportedOperationException();
+        });
         record.enqueueTime = enqueueTime;
+        record.enqueueRealTime = enqueueTime;
+        record.enqueueClockTime = enqueueTime;
     }
 
     @Test
@@ -370,7 +384,7 @@
                 .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, options,
                 List.of(makeMockRegisteredReceiver()), false);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
+        enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
 
         queue.setProcessAndUidCached(null, false);
         final long notCachedRunnableAt = queue.getRunnableAt();
@@ -397,7 +411,7 @@
                 .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_NONE);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, options,
                 List.of(makeMockRegisteredReceiver()), false);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
+        enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
 
         queue.setProcessAndUidCached(null, false);
         final long notCachedRunnableAt = queue.getRunnableAt();
@@ -421,12 +435,12 @@
         // enqueue a bg-priority broadcast then a fg-priority one
         final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
         final BroadcastRecord timezoneRecord = makeBroadcastRecord(timezone);
-        queue.enqueueOrReplaceBroadcast(timezoneRecord, 0, false);
+        enqueueOrReplaceBroadcast(queue, timezoneRecord, 0);
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
+        enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
 
         // verify that:
         // (a) the queue is immediately runnable by existence of a fg-priority broadcast
@@ -457,7 +471,7 @@
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, null,
                 List.of(withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
                         withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 0)), true);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 1, false);
+        enqueueOrReplaceBroadcast(queue, airplaneRecord, 1);
 
         assertFalse(queue.isRunnable());
         assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
@@ -481,7 +495,7 @@
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane,
                 List.of(makeMockRegisteredReceiver()));
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
+        enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
 
         mConstants.MAX_PENDING_BROADCASTS = 128;
         queue.invalidateRunnableAt();
@@ -507,11 +521,11 @@
                 new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED),
                 List.of(makeMockRegisteredReceiver()));
 
-        queue.enqueueOrReplaceBroadcast(lazyRecord, 0, false);
+        enqueueOrReplaceBroadcast(queue, lazyRecord, 0);
         assertThat(queue.getRunnableAt()).isGreaterThan(lazyRecord.enqueueTime);
         assertThat(queue.getRunnableAtReason()).isNotEqualTo(testRunnableAtReason);
 
-        queue.enqueueOrReplaceBroadcast(testRecord, 0, false);
+        enqueueOrReplaceBroadcast(queue, testRecord, 0);
         assertThat(queue.getRunnableAt()).isAtMost(testRecord.enqueueTime);
         assertThat(queue.getRunnableAtReason()).isEqualTo(testRunnableAtReason);
     }
@@ -573,22 +587,22 @@
         BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
                 PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
 
-        queue.enqueueOrReplaceBroadcast(
+        enqueueOrReplaceBroadcast(queue,
                 makeBroadcastRecord(new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
-                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, false);
-        queue.enqueueOrReplaceBroadcast(
-                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0, false);
-        queue.enqueueOrReplaceBroadcast(
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0);
+        enqueueOrReplaceBroadcast(queue,
                 makeBroadcastRecord(new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
-                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, false);
-        queue.enqueueOrReplaceBroadcast(
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+        enqueueOrReplaceBroadcast(queue,
                 makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)
-                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, false);
-        queue.enqueueOrReplaceBroadcast(
-                makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0, false);
-        queue.enqueueOrReplaceBroadcast(
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0);
+        enqueueOrReplaceBroadcast(queue,
                 makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
-                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, false);
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
 
         queue.makeActiveNextPending();
         assertEquals(Intent.ACTION_LOCKED_BOOT_COMPLETED, queue.getActive().intent.getAction());
@@ -1164,8 +1178,8 @@
 
         final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK)
                 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(timeTick,
-                List.of(makeMockRegisteredReceiver())), 0, false);
+        enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(timeTick,
+                List.of(makeMockRegisteredReceiver())), 0);
         assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
 
         // Make the foreground broadcast as active.
@@ -1176,15 +1190,15 @@
         assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(airplane,
-                List.of(makeMockRegisteredReceiver())), 0, false);
+        enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(airplane,
+                List.of(makeMockRegisteredReceiver())), 0);
 
         // Make the background broadcast as active.
         queue.makeActiveNextPending();
         assertEquals(ProcessList.SCHED_GROUP_BACKGROUND, queue.getPreferredSchedulingGroupLocked());
 
-        queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(timeTick,
-                List.of(makeMockRegisteredReceiver())), 0, false);
+        enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(timeTick,
+                List.of(makeMockRegisteredReceiver())), 0);
         // Even though the active broadcast is not a foreground one, scheduling group will be
         // DEFAULT since there is a foreground broadcast waiting to be delivered.
         assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked());
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
new file mode 100644
index 0000000..541b077
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.rollback;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.VersionedPackage;
+import android.content.rollback.PackageRollbackInfo;
+import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
+import android.util.Log;
+import android.util.Xml;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.PackageWatchdog;
+import com.android.server.SystemConfig;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.List;
+import java.util.Scanner;
+
+
+@RunWith(AndroidJUnit4.class)
+public class RollbackPackageHealthObserverTest {
+    @Mock
+    private Context mMockContext;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private PackageWatchdog mMockPackageWatchdog;
+    @Mock
+    RollbackManager mRollbackManager;
+    @Mock
+    RollbackInfo mRollbackInfo;
+    @Mock
+    PackageRollbackInfo mPackageRollbackInfo;
+
+    private MockitoSession mSession;
+    private static final String APP_A = "com.package.a";
+    private static final long VERSION_CODE = 1L;
+    private static final String LOG_TAG = "RollbackPackageHealthObserverTest";
+
+    private SystemConfig mSysConfig;
+
+    @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+    @Before
+    public void setup() {
+        mSysConfig = new SystemConfigTestClass();
+
+        mSession = ExtendedMockito.mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .spyStatic(PackageWatchdog.class)
+                .startMocking();
+
+        // Mock PackageWatchdog
+        doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog)
+                .when(() -> PackageWatchdog.getInstance(mMockContext));
+
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mSession.finishMocking();
+    }
+
+    /**
+     * Subclass of SystemConfig without running the constructor.
+     */
+    private class SystemConfigTestClass extends SystemConfig {
+        SystemConfigTestClass() {
+            super(false);
+        }
+    }
+
+    @Test
+    public void testHealthCheckLevels() {
+        RollbackPackageHealthObserver observer =
+                spy(new RollbackPackageHealthObserver(mMockContext));
+        VersionedPackage testFailedPackage = new VersionedPackage(APP_A, VERSION_CODE);
+
+
+        when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+
+        // Crashes with no rollbacks available
+        assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+                observer.onHealthCheckFailed(null,
+                        PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1));
+        assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+                observer.onHealthCheckFailed(null,
+                        PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
+
+        // Make the rollbacks available
+        when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(mRollbackInfo));
+        when(mRollbackInfo.getPackages()).thenReturn(List.of(mPackageRollbackInfo));
+        when(mPackageRollbackInfo.getVersionRolledBackFrom()).thenReturn(testFailedPackage);
+
+        // native crash
+        assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
+                observer.onHealthCheckFailed(null,
+                        PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1));
+        // non-native crash
+        assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
+                observer.onHealthCheckFailed(testFailedPackage,
+                        PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
+        // Second non-native crash again
+        assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+                observer.onHealthCheckFailed(testFailedPackage,
+                        PackageWatchdog.FAILURE_REASON_APP_CRASH, 2));
+        // Subsequent crashes when rollbacks have completed
+        when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of());
+        assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+                observer.onHealthCheckFailed(testFailedPackage,
+                        PackageWatchdog.FAILURE_REASON_APP_CRASH, 3));
+    }
+
+    /**
+     * Test that isAutomaticRollbackDenied works correctly when packages that are not
+     * denied are sent.
+     */
+    @Test
+    public void isRollbackAllowedTest_false() throws IOException {
+        final String contents =
+                "<config>\n"
+                + "    <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
+                + "</config>";
+        final File folder = createTempSubfolder("folder");
+        createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
+
+        readPermissions(folder, /* Grant all permission flags */ ~0);
+
+        assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
+                new VersionedPackage("com.test.package", 1))).isEqualTo(false);
+    }
+
+    /**
+     * Test that isAutomaticRollbackDenied works correctly when packages that are
+     * denied are sent.
+     */
+    @Test
+    public void isRollbackAllowedTest_true() throws IOException {
+        final String contents =
+                "<config>\n"
+                + "    <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
+                + "</config>";
+        final File folder = createTempSubfolder("folder");
+        createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
+
+        readPermissions(folder, /* Grant all permission flags */ ~0);
+
+        assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
+                new VersionedPackage("com.android.vending", 1))).isEqualTo(true);
+    }
+
+    /**
+     * Test that isAutomaticRollbackDenied works correctly when no config is present
+     */
+    @Test
+    public void isRollbackAllowedTest_noConfig() throws IOException {
+        final File folder = createTempSubfolder("folder");
+
+        readPermissions(folder, /* Grant all permission flags */ ~0);
+
+        assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
+                new VersionedPackage("com.android.vending", 1))).isEqualTo(false);
+    }
+
+    /**
+     * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
+     *
+     * @param folder   pre-existing subdirectory of mTemporaryFolder to put the file
+     * @param fileName name of the file (e.g. filename.xml) to create
+     * @param contents contents to write to the file
+     * @return the newly created file
+     */
+    private File createTempFile(File folder, String fileName, String contents)
+            throws IOException {
+        File file = new File(folder, fileName);
+        BufferedWriter bw = new BufferedWriter(new FileWriter(file));
+        bw.write(contents);
+        bw.close();
+
+        // Print to logcat for test debugging.
+        Log.d(LOG_TAG, "Contents of file " + file.getAbsolutePath());
+        Scanner input = new Scanner(file);
+        while (input.hasNextLine()) {
+            Log.d(LOG_TAG, input.nextLine());
+        }
+
+        return file;
+    }
+
+    private void readPermissions(File libraryDir, int permissionFlag) {
+        final XmlPullParser parser = Xml.newPullParser();
+        mSysConfig.readPermissions(parser, libraryDir, permissionFlag);
+    }
+
+    /**
+     * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
+     *
+     * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed
+     * @return the folder
+     */
+    private File createTempSubfolder(String folderName)
+            throws IOException {
+        File folder = new File(mTemporaryFolder.getRoot(), folderName);
+        folder.mkdirs();
+        return folder;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 7468901..d5d06d3 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.face.aidl;
 
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -148,6 +150,28 @@
     }
 
     @Test
+    public void testLockoutEndsOperation() throws RemoteException {
+        final FaceAuthenticationClient client = createClient(2);
+        client.start(mCallback);
+        client.onLockoutPermanent();
+
+        verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(),
+                eq(FACE_ERROR_LOCKOUT_PERMANENT), anyInt());
+        verify(mCallback).onClientFinished(client, false);
+    }
+
+    @Test
+    public void testTemporaryLockoutEndsOperation() throws RemoteException {
+        final FaceAuthenticationClient client = createClient(2);
+        client.start(mCallback);
+        client.onLockoutTimed(1000);
+
+        verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(),
+                eq(FACE_ERROR_LOCKOUT), anyInt());
+        verify(mCallback).onClientFinished(client, false);
+    }
+
+    @Test
     public void notifyHalWhenContextChanges() throws RemoteException {
         final FaceAuthenticationClient client = createClient();
         client.start(mCallback);
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
deleted file mode 100644
index 0be678a..0000000
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.rollback;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.pm.VersionedPackage;
-import android.util.Log;
-import android.util.Xml;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.SystemConfig;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.xmlpull.v1.XmlPullParser;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.Scanner;
-
-@RunWith(AndroidJUnit4.class)
-public class RollbackPackageHealthObserverTest {
-    private static final String LOG_TAG = "RollbackPackageHealthObserverTest";
-
-    private SystemConfig mSysConfig;
-
-    @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
-
-    @Before
-    public void setup() {
-        mSysConfig = new SystemConfigTestClass();
-    }
-
-    /**
-    * Subclass of SystemConfig without running the constructor.
-    */
-    private class SystemConfigTestClass extends SystemConfig {
-        SystemConfigTestClass() {
-          super(false);
-        }
-    }
-
-    /**
-     * Test that isAutomaticRollbackDenied works correctly when packages that are not
-     * denied are sent.
-     */
-    @Test
-    public void isRollbackAllowedTest_false() throws IOException {
-        final String contents =
-                "<config>\n"
-                + "    <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
-                + "</config>";
-        final File folder = createTempSubfolder("folder");
-        createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
-
-        readPermissions(folder, /* Grant all permission flags */ ~0);
-
-        assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
-            new VersionedPackage("com.test.package", 1))).isEqualTo(false);
-    }
-
-    /**
-     * Test that isAutomaticRollbackDenied works correctly when packages that are
-     * denied are sent.
-     */
-    @Test
-    public void isRollbackAllowedTest_true() throws IOException {
-        final String contents =
-                "<config>\n"
-                + "    <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
-                + "</config>";
-        final File folder = createTempSubfolder("folder");
-        createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
-
-        readPermissions(folder, /* Grant all permission flags */ ~0);
-
-        assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
-            new VersionedPackage("com.android.vending", 1))).isEqualTo(true);
-    }
-
-    /**
-     * Test that isAutomaticRollbackDenied works correctly when no config is present
-     */
-    @Test
-    public void isRollbackAllowedTest_noConfig() throws IOException {
-        final File folder = createTempSubfolder("folder");
-
-        readPermissions(folder, /* Grant all permission flags */ ~0);
-
-        assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
-            new VersionedPackage("com.android.vending", 1))).isEqualTo(false);
-    }
-
-    /**
-     * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
-     *
-     * @param folder   pre-existing subdirectory of mTemporaryFolder to put the file
-     * @param fileName name of the file (e.g. filename.xml) to create
-     * @param contents contents to write to the file
-     * @return the newly created file
-     */
-    private File createTempFile(File folder, String fileName, String contents)
-            throws IOException {
-        File file = new File(folder, fileName);
-        BufferedWriter bw = new BufferedWriter(new FileWriter(file));
-        bw.write(contents);
-        bw.close();
-
-        // Print to logcat for test debugging.
-        Log.d(LOG_TAG, "Contents of file " + file.getAbsolutePath());
-        Scanner input = new Scanner(file);
-        while (input.hasNextLine()) {
-            Log.d(LOG_TAG, input.nextLine());
-        }
-
-        return file;
-    }
-
-    private void readPermissions(File libraryDir, int permissionFlag) {
-        final XmlPullParser parser = Xml.newPullParser();
-        mSysConfig.readPermissions(parser, libraryDir, permissionFlag);
-    }
-
-    /**
-     * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
-     *
-     * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed
-     * @return the folder
-     */
-    private File createTempSubfolder(String folderName)
-            throws IOException {
-        File folder = new File(mTemporaryFolder.getRoot(), folderName);
-        folder.mkdirs();
-        return folder;
-    }
-}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index ff6c976..516fb4a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -15,23 +15,31 @@
  */
 package com.android.server.notification;
 
-import static com.android.server.notification.GroupHelper.AUTOGROUP_KEY;
+import static android.app.Notification.FLAG_AUTO_CANCEL;
+import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.Notification.FLAG_CAN_COLORIZE;
+import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
+import static android.app.Notification.FLAG_NO_CLEAR;
+import static android.app.Notification.FLAG_ONGOING_EVENT;
+
+import static com.android.server.notification.GroupHelper.BASE_FLAGS;
 
 import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
 
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 
+import android.annotation.SuppressLint;
 import android.app.Notification;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.ArrayMap;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -45,11 +53,10 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
-import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.Map;
 
 @SmallTest
+@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the class.
 @RunWith(AndroidJUnit4.class)
 public class GroupHelperTest extends UiServiceTestCase {
     private @Mock GroupHelper.Callback mCallback;
@@ -82,21 +89,104 @@
     }
 
     @Test
-    public void testNoGroup_postingUnderLimit() throws Exception {
+    public void testGetAutogroupSummaryFlags_noChildren() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+
+        assertEquals(BASE_FLAGS, mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testGetAutogroupSummaryFlags_oneOngoing() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+        children.put("a", 0);
+        children.put("b", FLAG_ONGOING_EVENT);
+        children.put("c", FLAG_BUBBLE);
+
+        assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
+                mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testGetAutogroupSummaryFlags_oneOngoingNoClear() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+        children.put("a", 0);
+        children.put("b", FLAG_ONGOING_EVENT|FLAG_NO_CLEAR);
+        children.put("c", FLAG_BUBBLE);
+
+        assertEquals(FLAG_NO_CLEAR | FLAG_ONGOING_EVENT | BASE_FLAGS,
+                mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testGetAutogroupSummaryFlags_oneOngoingBubble() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+        children.put("a", 0);
+        children.put("b", FLAG_ONGOING_EVENT | FLAG_BUBBLE);
+        children.put("c", FLAG_BUBBLE);
+
+        assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
+                mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testGetAutogroupSummaryFlags_multipleOngoing() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+        children.put("a", 0);
+        children.put("b", FLAG_ONGOING_EVENT);
+        children.put("c", FLAG_BUBBLE);
+        children.put("d", FLAG_ONGOING_EVENT);
+
+        assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
+                mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testGetAutogroupSummaryFlags_oneAutoCancel() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+        children.put("a", 0);
+        children.put("b", FLAG_AUTO_CANCEL);
+        children.put("c", FLAG_BUBBLE);
+
+        assertEquals(BASE_FLAGS,
+                mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testGetAutogroupSummaryFlags_allAutoCancel() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+        children.put("a", FLAG_AUTO_CANCEL);
+        children.put("b", FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE);
+        children.put("c", FLAG_AUTO_CANCEL);
+        children.put("d", FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE);
+
+        assertEquals(FLAG_AUTO_CANCEL | BASE_FLAGS,
+                mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testGetAutogroupSummaryFlags_allAutoCancelOneOngoing() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+        children.put("a", FLAG_AUTO_CANCEL);
+        children.put("b", FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE);
+        children.put("c", FLAG_AUTO_CANCEL);
+        children.put("d", FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE | FLAG_ONGOING_EVENT);
+
+        assertEquals(FLAG_AUTO_CANCEL| FLAG_ONGOING_EVENT | BASE_FLAGS,
+                mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testNoGroup_postingUnderLimit() {
         final String pkg = "package";
         for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
             mGroupHelper.onNotificationPosted(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM),
                     false);
         }
-        verify(mCallback, never()).addAutoGroupSummary(
-                eq(UserHandle.USER_SYSTEM), eq(pkg), anyString(), anyBoolean());
-        verify(mCallback, never()).addAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verifyZeroInteractions(mCallback);
     }
 
     @Test
-    public void testNoGroup_multiPackage() throws Exception {
+    public void testNoGroup_multiPackage() {
         final String pkg = "package";
         final String pkg2 = "package2";
         for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
@@ -105,31 +195,23 @@
         }
         mGroupHelper.onNotificationPosted(
                 getSbn(pkg2, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM), false);
-        verify(mCallback, never()).addAutoGroupSummary(
-                eq(UserHandle.USER_SYSTEM), eq(pkg), anyString(), anyBoolean());
-        verify(mCallback, never()).addAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verifyZeroInteractions(mCallback);
     }
 
     @Test
-    public void testNoGroup_multiUser() throws Exception {
+    public void testNoGroup_multiUser() {
         final String pkg = "package";
         for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
             mGroupHelper.onNotificationPosted(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM),
                     false);
         }
         mGroupHelper.onNotificationPosted(
-                getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.ALL), false);
-        verify(mCallback, never()).addAutoGroupSummary(
-                anyInt(), eq(pkg), anyString(), anyBoolean());
-        verify(mCallback, never()).addAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+                getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.of(7)), false);
+        verifyZeroInteractions(mCallback);
     }
 
     @Test
-    public void testNoGroup_someAreGrouped() throws Exception {
+    public void testNoGroup_someAreGrouped() {
         final String pkg = "package";
         for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
             mGroupHelper.onNotificationPosted(
@@ -137,233 +219,344 @@
         }
         mGroupHelper.onNotificationPosted(
                 getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM, "a"), false);
-        verify(mCallback, never()).addAutoGroupSummary(
-                eq(UserHandle.USER_SYSTEM), eq(pkg), anyString(), anyBoolean());
-        verify(mCallback, never()).addAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verifyZeroInteractions(mCallback);
     }
 
     @Test
-    public void testPostingOverLimit() throws Exception {
+    public void testAddSummary() {
         final String pkg = "package";
         for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
             mGroupHelper.onNotificationPosted(
                     getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false);
         }
-        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(false));
+        verify(mCallback, times(1)).addAutoGroupSummary(
+                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
     }
 
     @Test
-    public void testPostingOverLimit_addsOngoingFlag() throws Exception {
+    public void testAddSummary_oneChildOngoing_summaryOngoing() {
         final String pkg = "package";
         for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
             StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
             if (i == 0) {
-                sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
+                sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
             }
             mGroupHelper.onNotificationPosted(sbn, false);
         }
-        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(true));
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
     }
 
     @Test
-    public void testAutoGroupCount_addingNoGroupSBN() {
+    public void testAddSummary_oneChildAutoCancel_summaryNotAutoCancel() {
         final String pkg = "package";
-        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
-        for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
-            notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            if (i == 0) {
+                sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+            }
+            mGroupHelper.onNotificationPosted(sbn, false);
         }
-
-        for (StatusBarNotification sbn: notifications) {
-            sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-            sbn.setOverrideGroupKey(AUTOGROUP_KEY);
-        }
-
-        for (StatusBarNotification sbn: notifications) {
-            mGroupHelper.onNotificationPosted(sbn, true);
-        }
-
-        verify(mCallback, times(AUTOGROUP_AT_COUNT + 1))
-            .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
-        int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg), AUTOGROUP_AT_COUNT + 1);
+        verify(mCallback, times(1)).addAutoGroupSummary(
+                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
     }
 
     @Test
-    public void testAutoGroupCount_UpdateNotification() {
+    public void testAddSummary_allChildrenAutoCancel_summaryAutoCancel() {
         final String pkg = "package";
-        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
-        for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
-            notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+            mGroupHelper.onNotificationPosted(sbn, false);
         }
-
-        for (StatusBarNotification sbn: notifications) {
-            sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-            sbn.setOverrideGroupKey(AUTOGROUP_KEY);
-        }
-
-        for (StatusBarNotification sbn: notifications) {
-            mGroupHelper.onNotificationPosted(sbn, true);
-        }
-
-        notifications.get(0).getNotification().flags &= ~Notification.FLAG_ONGOING_EVENT;
-        mGroupHelper.onNotificationUpdated(notifications.get(0));
-
-        verify(mCallback, times(AUTOGROUP_AT_COUNT + 2))
-                .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
-        int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg), AUTOGROUP_AT_COUNT);
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(BASE_FLAGS | FLAG_AUTO_CANCEL));
+        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
     }
 
     @Test
-    public void testAutoGroupCount_UpdateNotificationAfterChanges() {
+    public void testAddSummary_summaryAutoCancelNoClear() {
         final String pkg = "package";
-        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
-        for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
-            notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+            if (i == 0) {
+                sbn.getNotification().flags |= FLAG_NO_CLEAR;
+            }
+            mGroupHelper.onNotificationPosted(sbn, false);
         }
-
-        for (StatusBarNotification sbn: notifications) {
-            sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-            sbn.setOverrideGroupKey(AUTOGROUP_KEY);
-        }
-
-        for (StatusBarNotification sbn: notifications) {
-            mGroupHelper.onNotificationPosted(sbn, true);
-        }
-
-        notifications.get(0).getNotification().flags &= ~Notification.FLAG_ONGOING_EVENT;
-
-        mGroupHelper.onNotificationUpdated(notifications.get(0));
-
-        notifications.get(0).getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-
-        mGroupHelper.onNotificationUpdated(notifications.get(0));
-
-        verify(mCallback, times(AUTOGROUP_AT_COUNT + 3))
-                .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
-        int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg), AUTOGROUP_AT_COUNT + 1);
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR));
+        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
     }
 
     @Test
-    public void testAutoGroupCount_RemoveNotification() {
+    public void testAutoGrouped_allOngoing_updateChildNotOngoing() {
         final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
         ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
-        for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
-            notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+            notifications.add(sbn);
         }
 
         for (StatusBarNotification sbn: notifications) {
-            sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-            sbn.setOverrideGroupKey(AUTOGROUP_KEY);
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // One notification is no longer ongoing
+        notifications.get(0).getNotification().flags &= ~FLAG_ONGOING_EVENT;
+        mGroupHelper.onNotificationPosted(notifications.get(0), true);
+
+        // Summary should keep FLAG_ONGOING_EVENT if any child has it
+        verify(mCallback).updateAutogroupSummary(
+                anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+    }
+
+    @Test
+    public void testAutoGrouped_singleOngoing_removeOngoingChild() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            if (i == 0) {
+                sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+            }
+            notifications.add(sbn);
         }
 
         for (StatusBarNotification sbn: notifications) {
-            mGroupHelper.onNotificationPosted(sbn, true);
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // remove ongoing
+        mGroupHelper.onNotificationRemoved(notifications.get(0));
+
+        // Summary is no longer ongoing
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+    }
+
+    @Test
+    public void testAutoGrouped_noOngoing_updateOngoingChild() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // update to ongoing
+        notifications.get(0).getNotification().flags |= FLAG_ONGOING_EVENT;
+        mGroupHelper.onNotificationPosted(notifications.get(0), true);
+
+        // Summary is now ongoing
+        verify(mCallback).updateAutogroupSummary(
+                anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+    }
+
+    @Test
+    public void testAutoGrouped_noOngoing_addOngoingChild() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // add ongoing
+        StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT + 1, null, UserHandle.SYSTEM);
+        sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+        mGroupHelper.onNotificationPosted(sbn, true);
+
+        // Summary is now ongoing
+        verify(mCallback).updateAutogroupSummary(
+                anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+    }
+
+    @Test
+    public void testAutoGrouped_singleOngoing_appGroupOngoingChild() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            if (i == 0) {
+                sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+            }
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // app group the ongoing child
+        StatusBarNotification sbn = getSbn(pkg, 0, "0", UserHandle.SYSTEM, "app group now");
+        mGroupHelper.onNotificationPosted(sbn, true);
+
+        // Summary is no longer ongoing
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+    }
+
+    @Test
+    public void testAutoGrouped_singleOngoing_removeNonOngoingChild() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            if (i == 0) {
+                sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+            }
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // remove ongoing
+        mGroupHelper.onNotificationRemoved(notifications.get(1));
+
+        // Summary is still ongoing
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+    }
+
+    @Test
+    public void testAutoGrouped_allAutoCancel_updateChildNotAutoCancel() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // One notification is no longer autocancelable
+        notifications.get(0).getNotification().flags &= ~FLAG_AUTO_CANCEL;
+        mGroupHelper.onNotificationPosted(notifications.get(0), true);
+
+        // Summary should no longer be autocancelable
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+    }
+
+    @Test
+    public void testAutoGrouped_almostAllAutoCancel_updateChildAutoCancel() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            if (i != 0) {
+                sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+            }
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // Missing notification is now autocancelable
+        notifications.get(0).getNotification().flags |= FLAG_AUTO_CANCEL;
+        mGroupHelper.onNotificationPosted(notifications.get(0), true);
+
+        // Summary should now autocancelable
+        verify(mCallback).updateAutogroupSummary(
+                anyInt(), anyString(), eq(BASE_FLAGS | FLAG_AUTO_CANCEL));
+    }
+
+    @Test
+    public void testAutoGrouped_allAutoCancel_updateChildAppGrouped() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // One notification is now grouped by app
+        StatusBarNotification sbn = getSbn(pkg, 0, "0", UserHandle.SYSTEM, "app group now");
+        mGroupHelper.onNotificationPosted(sbn, true);
+
+        // Summary should be still be autocancelable
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+    }
+
+    @Test
+    public void testAutoGrouped_allAutoCancel_removeChild() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
         }
 
         mGroupHelper.onNotificationRemoved(notifications.get(0));
 
-        verify(mCallback, times(AUTOGROUP_AT_COUNT + 2))
-                .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
-        int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg), AUTOGROUP_AT_COUNT);
-    }
-
-
-    @Test
-    public void testAutoGroupCount_UpdateToNoneOngoingNotification() {
-        final String pkg = "package";
-        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
-        for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
-            notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
-        }
-
-        for (StatusBarNotification sbn: notifications) {
-            sbn.setOverrideGroupKey(AUTOGROUP_KEY);
-        }
-
-        for (StatusBarNotification sbn: notifications) {
-            mGroupHelper.onNotificationPosted(sbn, true);
-        }
-
-        notifications.get(0).getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-        mGroupHelper.onNotificationUpdated(notifications.get(0));
-
-        verify(mCallback, times(1))
-                .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
-        int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg), 1);
+        // Summary should still be autocancelable
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
     }
 
     @Test
-    public void testAutoGroupCount_AddOneOngoingNotification() {
-        final String pkg = "package";
-        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
-        for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
-            notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
-        }
-        StatusBarNotification sbn = notifications.get(AUTOGROUP_AT_COUNT);
-        sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-        sbn.setOverrideGroupKey(AUTOGROUP_KEY);
-
-
-        for (StatusBarNotification current: notifications) {
-            mGroupHelper.onNotificationPosted(current, true);
-        }
-
-        verify(mCallback, times(1))
-                .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
-        int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg), 1);
-    }
-
-    @Test
-    public void testAutoGroupCount_UpdateNoneOngoing() {
-        final String pkg = "package";
-        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
-        for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
-            notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
-        }
-
-        for (StatusBarNotification sbn: notifications) {
-            sbn.setOverrideGroupKey(AUTOGROUP_KEY);
-        }
-
-        for (StatusBarNotification sbn: notifications) {
-            mGroupHelper.onNotificationPosted(sbn, true);
-        }
-
-        verify(mCallback, times(0))
-                .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
-        int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(userId, pkg), 0);
-    }
-
-
-    @Test
-    public void testDropToZeroRemoveGroup() throws Exception {
+    public void testDropToZeroRemoveGroup() {
         final String pkg = "package";
         List<StatusBarNotification> posted = new ArrayList<>();
         for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -371,7 +564,8 @@
             posted.add(sbn);
             mGroupHelper.onNotificationPosted(sbn, false);
         }
-        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(false));
+        verify(mCallback, times(1)).addAutoGroupSummary(
+                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -390,7 +584,7 @@
     }
 
     @Test
-    public void testAppStartsGrouping() throws Exception {
+    public void testAppStartsGrouping() {
         final String pkg = "package";
         List<StatusBarNotification> posted = new ArrayList<>();
         for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -399,7 +593,7 @@
             mGroupHelper.onNotificationPosted(sbn, false);
         }
         verify(mCallback, times(1)).addAutoGroupSummary(
-                anyInt(), eq(pkg), anyString(), anyBoolean());
+                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -408,9 +602,10 @@
         for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
             final StatusBarNotification sbn =
                     getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, "app group");
-            mGroupHelper.onNotificationPosted(sbn, false);
+            sbn.setOverrideGroupKey("autogrouped");
+            mGroupHelper.onNotificationPosted(sbn, true);
             verify(mCallback, times(1)).removeAutoGroup(sbn.getKey());
-            if (i < AUTOGROUP_AT_COUNT -1) {
+            if (i < AUTOGROUP_AT_COUNT - 1) {
                 verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
             }
         }
@@ -418,8 +613,7 @@
     }
 
     @Test
-    public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled()
-            throws Exception {
+    public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled() {
         final String pkg = "package";
         List<StatusBarNotification> posted = new ArrayList<>();
         for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -427,7 +621,8 @@
             posted.add(sbn);
             mGroupHelper.onNotificationPosted(sbn, false);
         }
-        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(false));
+        verify(mCallback, times(1)).addAutoGroupSummary(
+                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -441,10 +636,7 @@
         Mockito.reset(mCallback);
 
         // only one child remains
-        Map<String, LinkedHashSet<String>> ungroupedForUser =
-                mGroupHelper.mUngroupedNotifications.get(UserHandle.USER_SYSTEM);
-        assertNotNull(ungroupedForUser);
-        assertEquals(1, ungroupedForUser.get(pkg).size());
+        assertEquals(1, mGroupHelper.getNotGroupedByAppCount(UserHandle.USER_SYSTEM, pkg));
 
         // Add new notification; it should be autogrouped even though the total count is
         // < AUTOGROUP_AT_COUNT
@@ -454,5 +646,8 @@
         verify(mCallback, times(1)).addAutoGroup(sbn.getKey());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+        verify(mCallback, never()).addAutoGroupSummary(
+                anyInt(), anyString(), anyString(), anyInt());
     }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index eceb589..9cfdaa7 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -754,13 +754,19 @@
 
     private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
             String groupKey, boolean isSummary) {
+        return generateNotificationRecord(channel, id, "tag" + System.currentTimeMillis(), groupKey,
+                isSummary);
+    }
+
+    private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
+            String tag, String groupKey, boolean isSummary) {
         Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
                 .setContentTitle("foo")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
                 .setGroup(groupKey)
                 .setGroupSummary(isSummary);
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id,
-                "tag" + System.currentTimeMillis(), mUid, 0,
+                tag, mUid, 0,
                 nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         return new NotificationRecord(mContext, sbn, channel);
     }
@@ -1899,7 +1905,8 @@
         mService.mSummaryByGroupKey.put("pkg", summary);
         mService.mAutobundledSummaries.put(0, new ArrayMap<>());
         mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey());
-        mService.updateAutobundledSummaryFlags(0, "pkg", true, false);
+        mService.updateAutobundledSummaryFlags(
+                0, "pkg", GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT, false);
 
         assertTrue(summary.getSbn().isOngoing());
     }
@@ -1915,7 +1922,7 @@
         mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey());
         mService.mSummaryByGroupKey.put("pkg", summary);
 
-        mService.updateAutobundledSummaryFlags(0, "pkg", false, false);
+        mService.updateAutobundledSummaryFlags(0, "pkg", GroupHelper.BASE_FLAGS, false);
 
         assertFalse(summary.getSbn().isOngoing());
     }
@@ -2897,7 +2904,7 @@
         when(mPermissionHelper.isPermissionFixed(PKG, temp.getUserId())).thenReturn(true);
 
         NotificationRecord r = mService.createAutoGroupSummary(
-                temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey(), false);
+                temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey(), 0);
 
         assertThat(r.isImportanceFixed()).isTrue();
     }
@@ -4213,7 +4220,7 @@
     }
 
     @Test
-    public void testOnlyAutogroupIfGroupChanged_noPriorNoti_autogroups() throws Exception {
+    public void testOnlyAutogroupIfNeeded_newNotification_ghUpdate() {
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
         mService.addEnqueuedNotification(r);
         NotificationManagerService.PostNotificationRunnable runnable =
@@ -4226,17 +4233,18 @@
     }
 
     @Test
-    public void testOnlyAutogroupIfGroupChanged_groupChanged_autogroups()
-            throws Exception {
-        NotificationRecord r =
-                generateNotificationRecord(mTestNotificationChannel, 0, "group", false);
+    public void testOnlyAutogroupIfNeeded_groupChanged_ghUpdate() {
+        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0,
+                "testOnlyAutogroupIfNeeded_groupChanged_ghUpdate", "group", false);
         mService.addNotification(r);
 
-        r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
-        mService.addEnqueuedNotification(r);
+        NotificationRecord update = generateNotificationRecord(mTestNotificationChannel, 0,
+                "testOnlyAutogroupIfNeeded_groupChanged_ghUpdate", null, false);
+        mService.addEnqueuedNotification(update);
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
-                        r.getUid(), SystemClock.elapsedRealtime());
+                mService.new PostNotificationRunnable(update.getKey(),
+                        update.getSbn().getPackageName(), update.getUid(),
+                        SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -4244,16 +4252,39 @@
     }
 
     @Test
-    public void testOnlyAutogroupIfGroupChanged_noGroupChanged_autogroups()
-            throws Exception {
-        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, "group",
-                false);
+    public void testOnlyAutogroupIfNeeded_flagsChanged_ghUpdate() {
+        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0,
+                "testOnlyAutogroupIfNeeded_flagsChanged_ghUpdate", "group", false);
         mService.addNotification(r);
-        mService.addEnqueuedNotification(r);
+
+        NotificationRecord update = generateNotificationRecord(mTestNotificationChannel, 0,
+                "testOnlyAutogroupIfNeeded_flagsChanged_ghUpdate", null, false);
+        update.getNotification().flags = FLAG_AUTO_CANCEL;
+        mService.addEnqueuedNotification(update);
+        NotificationManagerService.PostNotificationRunnable runnable =
+                mService.new PostNotificationRunnable(update.getKey(),
+                        update.getSbn().getPackageName(), update.getUid(),
+                        SystemClock.elapsedRealtime());
+        runnable.run();
+        waitForIdle();
+
+        verify(mGroupHelper, times(1)).onNotificationPosted(any(), anyBoolean());
+    }
+
+    @Test
+    public void testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate() {
+        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0,
+                "testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate", null, false);
+        mService.addNotification(r);
+        NotificationRecord update = generateNotificationRecord(mTestNotificationChannel, 0,
+                "testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate", null, false);
+        update.getNotification().color = Color.BLACK;
+        mService.addEnqueuedNotification(update);
 
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
-                        r.getUid(), SystemClock.elapsedRealtime());
+                mService.new PostNotificationRunnable(update.getKey(),
+                        update.getSbn().getPackageName(),
+                        update.getUid(), SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -10220,10 +10251,10 @@
 
         // grouphelper is a mock here, so make the calls it would make
 
-        // add summary; wait for it to be posted
-        mService.addAutoGroupSummary(nr1.getUserId(), nr1.getSbn().getPackageName(), nr1.getKey(),
-                true);
-        waitForIdle();
+        // add summary
+        mService.addNotification(mService.createAutoGroupSummary(nr1.getUserId(),
+                nr1.getSbn().getPackageName(), nr1.getKey(),
+                GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT));
 
         // cancel both children
         mBinderService.cancelNotificationWithTag(PKG, PKG, nr0.getSbn().getTag(),
@@ -10232,9 +10263,7 @@
                 nr1.getSbn().getId(), nr1.getSbn().getUserId());
         waitForIdle();
 
-        // group helper would send 'remove flag' and then 'remove summary' events
-        mService.updateAutobundledSummaryFlags(nr1.getUserId(), nr1.getSbn().getPackageName(),
-                false, false);
+        // group helper would send 'remove summary' event
         mService.clearAutogroupSummaryLocked(nr1.getUserId(), nr1.getSbn().getPackageName());
         waitForIdle();
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java
index e6569f7..9fe0e49 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java
@@ -98,6 +98,41 @@
     }
 
     @Test
+    public void testHasDiffs_autoBundled() {
+        NotificationRecord r = generateRecord();
+
+        NotificationRecordExtractorData extractorData = new NotificationRecordExtractorData(
+                1,
+                r.getPackageVisibilityOverride(),
+                r.canShowBadge(),
+                r.canBubble(),
+                r.getNotification().isBubbleNotification(),
+                r.getChannel(),
+                r.getGroupKey(),
+                r.getPeopleOverride(),
+                r.getSnoozeCriteria(),
+                r.getUserSentiment(),
+                r.getSuppressedVisualEffects(),
+                r.getSystemGeneratedSmartActions(),
+                r.getSmartReplies(),
+                r.getImportance(),
+                r.getRankingScore(),
+                r.isConversation(),
+                r.getProposedImportance(),
+                r.hasSensitiveContent());
+
+        Bundle signals = new Bundle();
+        signals.putString(Adjustment.KEY_GROUP_KEY, "ranker_group");
+        Adjustment adjustment = new Adjustment("pkg", r.getKey(), signals, "", 0);
+        r.addAdjustment(adjustment);
+        NotificationAdjustmentExtractor adjustmentExtractor = new NotificationAdjustmentExtractor();
+        adjustmentExtractor.process(r);
+
+        assertTrue(extractorData.hasDiffForRankingLocked(r, 1));
+        assertTrue(extractorData.hasDiffForLoggingLocked(r, 1));
+    }
+
+    @Test
     public void testHasDiffs_sensitiveContentChange() {
         NotificationRecord r = generateRecord();
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index c131c84..7092b0b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -104,7 +104,6 @@
         when(mMockRunner.asBinder()).thenReturn(new Binder());
         mController = spy(new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks,
                 DEFAULT_DISPLAY));
-        mController.mShouldAttachNavBarToAppDuringTransition = false;
         mRootHomeTask = mDefaultDisplay.getDefaultTaskDisplayArea().getRootHomeTask();
         assertNotNull(mRootHomeTask);
     }
@@ -814,13 +813,13 @@
     }
 
     private void setupForShouldAttachNavBarDuringTransition() {
-        mController.mShouldAttachNavBarToAppDuringTransition = true;
         final WindowState navBar = spy(createWindow(null, TYPE_NAVIGATION_BAR, "NavigationBar"));
         mDefaultDisplay.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs);
         mWm.setRecentsAnimationController(mController);
         doReturn(navBar).when(mController).getNavigationBarWindow();
         final DisplayPolicy displayPolicy = spy(mDefaultDisplay.getDisplayPolicy());
         doReturn(displayPolicy).when(mDefaultDisplay).getDisplayPolicy();
+        doReturn(true).when(displayPolicy).shouldAttachNavBarToAppDuringTransition();
     }
 
     private static void initializeRecentsAnimationController(RecentsAnimationController controller,
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 1e2fdec..43b429c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1410,9 +1410,9 @@
         final Transition.ChangeInfo task1ChangeInfo = closeTransition.mChanges.get(task1);
         assertNotNull(task1ChangeInfo);
         assertTrue(task1ChangeInfo.hasChanged());
+        // Make sure the unrelated activity is NOT collected.
         final Transition.ChangeInfo activity1ChangeInfo = closeTransition.mChanges.get(activity1);
-        assertNotNull(activity1ChangeInfo);
-        assertTrue(activity1ChangeInfo.hasChanged());
+        assertNull(activity1ChangeInfo);
         // No need to wait for the activity in transient hide task.
         assertEquals(WindowContainer.SYNC_STATE_NONE, activity1.mSyncState);
 
@@ -1442,6 +1442,7 @@
                 }
             }
         });
+        assertTrue(activity1.isVisible());
         controller.finishTransition(closeTransition);
         assertTrue(wasInFinishingTransition[0]);
         assertNull(controller.mFinishingTransition);
@@ -1450,6 +1451,7 @@
         assertEquals(ActivityTaskManagerService.APP_SWITCH_DISALLOW, mAtm.getBalAppSwitchesState());
         // Because task1 is occluded by task2, finishTransition should make activity1 invisible.
         assertFalse(activity1.isVisibleRequested());
+        // Make sure activity1 visibility was committed
         assertFalse(activity1.isVisible());
         assertFalse(activity1.app.hasActivityInVisibleTask());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 6cf2b2d..74ba45c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -500,7 +500,6 @@
         RecentsAnimationController controller = new RecentsAnimationController(
                 mWm, mockRunner, null, displayId);
         spyOn(controller);
-        controller.mShouldAttachNavBarToAppDuringTransition = true;
         doReturn(mNavBarWindow).when(controller).getNavigationBarWindow();
         mWm.setRecentsAnimationController(controller);
 
@@ -508,6 +507,10 @@
         spyOn(mDisplayContent.mInputMethodWindow);
         doReturn(true).when(mDisplayContent.mInputMethodWindow).isVisible();
 
+        DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
+        spyOn(policy);
+        doReturn(true).when(policy).shouldAttachNavBarToAppDuringTransition();
+
         // create home activity
         Task rootHomeTask = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
         final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService)
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
index 337e1f9..7fe8582 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
@@ -27,6 +27,8 @@
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.server.audio.AudioService;
 
+import java.util.Arrays;
+
 /**
  * Represents the ALSA specification, and attributes of an ALSA device.
  */
@@ -36,17 +38,21 @@
 
     private final int mCardNum;
     private final int mDeviceNum;
+    private final String mAlsaCardDeviceString;
     private final String mDeviceAddress;
-    private final boolean mHasOutput;
-    private final boolean mHasInput;
 
-    private final boolean mIsInputHeadset;
-    private final boolean mIsOutputHeadset;
+    // The following two constant will be used as index to access arrays.
+    private static final int INPUT = 0;
+    private static final int OUTPUT = 1;
+    private static final int NUM_DIRECTIONS = 2;
+    private static final String[] DIRECTION_STR = {"INPUT", "OUTPUT"};
+    private final boolean[] mHasDevice = new boolean[NUM_DIRECTIONS];
+
+    private final boolean[] mIsHeadset = new boolean[NUM_DIRECTIONS];
     private final boolean mIsDock;
-
-    private boolean mSelected = false;
-    private int mOutputState;
-    private int mInputState;
+    private final int[] mDeviceType = new int[NUM_DIRECTIONS];
+    private boolean[] mIsSelected = new boolean[NUM_DIRECTIONS];
+    private int[] mState = new int[NUM_DIRECTIONS];
     private UsbAlsaJackDetector mJackDetector;
     private IAudioService mAudioService;
 
@@ -60,11 +66,13 @@
         mCardNum = card;
         mDeviceNum = device;
         mDeviceAddress = deviceAddress;
-        mHasOutput = hasOutput;
-        mHasInput = hasInput;
-        mIsInputHeadset = isInputHeadset;
-        mIsOutputHeadset = isOutputHeadset;
+        mHasDevice[OUTPUT] = hasOutput;
+        mHasDevice[INPUT] = hasInput;
+        mIsHeadset[INPUT] = isInputHeadset;
+        mIsHeadset[OUTPUT] = isOutputHeadset;
         mIsDock = isDock;
+        initDeviceType();
+        mAlsaCardDeviceString = getAlsaCardDeviceString();
     }
 
     /**
@@ -104,28 +112,28 @@
      * @return true if the device supports output.
      */
     public boolean hasOutput() {
-        return mHasOutput;
+        return mHasDevice[OUTPUT];
     }
 
     /**
      * @return true if the device supports input (recording).
      */
     public boolean hasInput() {
-        return mHasInput;
-    }
-
-    /**
-     * @return true if the device is a headset for purposes of input.
-     */
-    public boolean isInputHeadset() {
-        return mIsInputHeadset;
+        return mHasDevice[INPUT];
     }
 
     /**
      * @return true if the device is a headset for purposes of output.
      */
     public boolean isOutputHeadset() {
-        return mIsOutputHeadset;
+        return mIsHeadset[OUTPUT];
+    }
+
+    /**
+     * @return true if the device is a headset for purposes of input.
+     */
+    public boolean isInputHeadset() {
+        return mIsHeadset[INPUT];
     }
 
     /**
@@ -157,6 +165,9 @@
 
     /** Begins a jack-detection thread. */
     private synchronized void startJackDetect() {
+        if (mJackDetector != null) {
+            return;
+        }
         // If no jack detect capabilities exist, mJackDetector will be null.
         mJackDetector = UsbAlsaJackDetector.startJackDetect(this);
     }
@@ -171,75 +182,152 @@
 
     /** Start using this device as the selected USB Audio Device. */
     public synchronized void start() {
-        mSelected = true;
-        mInputState = 0;
-        mOutputState = 0;
+        startInput();
+        startOutput();
+    }
+
+    /** Start using this device as the selected USB input device. */
+    public synchronized void startInput() {
+        startDevice(INPUT);
+    }
+
+    /** Start using this device as selected USB output device. */
+    public synchronized void startOutput() {
+        startDevice(OUTPUT);
+    }
+
+    private void startDevice(int direction) {
+        if (mIsSelected[direction]) {
+            return;
+        }
+        mIsSelected[direction] = true;
+        mState[direction] = 0;
         startJackDetect();
-        updateWiredDeviceConnectionState(true);
+        updateWiredDeviceConnectionState(direction, true /*enable*/);
     }
 
     /** Stop using this device as the selected USB Audio Device. */
     public synchronized void stop() {
-        stopJackDetect();
-        updateWiredDeviceConnectionState(false);
-        mSelected = false;
+        stopInput();
+        stopOutput();
     }
 
-    /** Updates AudioService with the connection state of the alsaDevice.
-     *  Checks ALSA Jack state for inputs and outputs before reporting.
+    /** Stop using this device as the selected USB input device. */
+    public synchronized void stopInput() {
+        if (!mIsSelected[INPUT]) {
+            return;
+        }
+        if (!mIsSelected[OUTPUT]) {
+            // Stop jack detection when both input and output are stopped
+            stopJackDetect();
+        }
+        updateInputWiredDeviceConnectionState(false /*enable*/);
+        mIsSelected[INPUT] = false;
+    }
+
+    /** Stop using this device as the selected USB output device. */
+    public synchronized void stopOutput() {
+        if (!mIsSelected[OUTPUT]) {
+            return;
+        }
+        if (!mIsSelected[INPUT]) {
+            // Stop jack detection when both input and output are stopped
+            stopJackDetect();
+        }
+        updateOutputWiredDeviceConnectionState(false /*enable*/);
+        mIsSelected[OUTPUT] = false;
+    }
+
+    private void initDeviceType() {
+        mDeviceType[INPUT] = mHasDevice[INPUT]
+                ? (mIsHeadset[INPUT] ? AudioSystem.DEVICE_IN_USB_HEADSET
+                                     : AudioSystem.DEVICE_IN_USB_DEVICE)
+                : AudioSystem.DEVICE_NONE;
+        mDeviceType[OUTPUT] = mHasDevice[OUTPUT]
+                ? (mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
+                           : (mIsHeadset[OUTPUT] ? AudioSystem.DEVICE_OUT_USB_HEADSET
+                                                 : AudioSystem.DEVICE_OUT_USB_DEVICE))
+                : AudioSystem.DEVICE_NONE;
+    }
+
+    /**
+     * @return the output device type that will be used to notify AudioService about device
+     *         connection. If there is no output on this device, {@link AudioSystem#DEVICE_NONE}
+     *         will be returned.
      */
-    public synchronized void updateWiredDeviceConnectionState(boolean enable) {
-        if (!mSelected) {
-            Slog.e(TAG, "updateWiredDeviceConnectionState on unselected AlsaDevice!");
-            return;
-        }
-        String alsaCardDeviceString = getAlsaCardDeviceString();
-        if (alsaCardDeviceString == null) {
-            return;
-        }
-        try {
-            // Output Device
-            if (mHasOutput) {
-                int device = mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
-                        : (mIsOutputHeadset
-                            ? AudioSystem.DEVICE_OUT_USB_HEADSET
-                            : AudioSystem.DEVICE_OUT_USB_DEVICE);
-                if (DEBUG) {
-                    Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(device)
-                            + " addr:" + alsaCardDeviceString
-                            + " name:" + mDeviceName);
-                }
-                boolean connected = isOutputJackConnected();
-                Slog.i(TAG, "OUTPUT JACK connected: " + connected);
-                int outputState = (enable && connected) ? 1 : 0;
-                if (outputState != mOutputState) {
-                    mOutputState = outputState;
-                    AudioDeviceAttributes attributes = new AudioDeviceAttributes(device,
-                            alsaCardDeviceString, mDeviceName);
-                    mAudioService.setWiredDeviceConnectionState(attributes, outputState, TAG);
-                }
-            }
-
-            // Input Device
-            if (mHasInput) {
-                int device = mIsInputHeadset
-                        ? AudioSystem.DEVICE_IN_USB_HEADSET
-                        : AudioSystem.DEVICE_IN_USB_DEVICE;
-                boolean connected = isInputJackConnected();
-                Slog.i(TAG, "INPUT JACK connected: " + connected);
-                int inputState = (enable && connected) ? 1 : 0;
-                if (inputState != mInputState) {
-                    mInputState = inputState;
-                    AudioDeviceAttributes attributes = new AudioDeviceAttributes(device,
-                            alsaCardDeviceString, mDeviceName);
-                    mAudioService.setWiredDeviceConnectionState(attributes, inputState, TAG);
-                }
-            }
-        } catch (RemoteException e) {
-            Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState");
-        }
+    public int getOutputDeviceType() {
+        return mDeviceType[OUTPUT];
     }
 
+    /**
+     * @return the input device type that will be used to notify AudioService about device
+     *         connection. If there is no input on this device, {@link AudioSystem#DEVICE_NONE}
+     *         will be returned.
+     */
+    public int getInputDeviceType() {
+        return mDeviceType[INPUT];
+    }
+
+    private boolean updateWiredDeviceConnectionState(int direction, boolean enable) {
+        if (!mIsSelected[direction]) {
+            Slog.e(TAG, "Updating wired device connection state on unselected device");
+            return false;
+        }
+        if (mDeviceType[direction] == AudioSystem.DEVICE_NONE) {
+            Slog.d(TAG,
+                    "Unable to set device connection state as " + DIRECTION_STR[direction]
+                    + " device type is none");
+            return false;
+        }
+        if (mAlsaCardDeviceString == null) {
+            Slog.w(TAG, "Failed to update " + DIRECTION_STR[direction] + " device connection "
+                    + "state failed as alsa card device string is null");
+            return false;
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(mDeviceType[direction])
+                    + " addr:" + mAlsaCardDeviceString
+                    + " name:" + mDeviceName);
+        }
+        boolean connected = direction == INPUT ? isInputJackConnected() : isOutputJackConnected();
+        Slog.i(TAG, DIRECTION_STR[direction] + " JACK connected: " + connected);
+        int state = (enable && connected) ? 1 : 0;
+        if (state != mState[direction]) {
+            mState[direction] = state;
+            AudioDeviceAttributes attributes = new AudioDeviceAttributes(
+                    mDeviceType[direction], mAlsaCardDeviceString, mDeviceName);
+            try {
+                mAudioService.setWiredDeviceConnectionState(attributes, state, TAG);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState for "
+                        + DIRECTION_STR[direction]);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Notify AudioService about the input device connection state.
+     *
+     * @param enable true to notify the device as connected.
+     * @return true only when it successfully notifies AudioService about the device
+     *         connection state.
+     */
+    public synchronized boolean updateInputWiredDeviceConnectionState(boolean enable) {
+        return updateWiredDeviceConnectionState(INPUT, enable);
+    }
+
+    /**
+     * Notify AudioService about the output device connection state.
+     *
+     * @param enable true to notify the device as connected.
+     * @return true only when it successfully notifies AudioService about the device
+     *         connection state.
+     */
+    public synchronized boolean updateOutputWiredDeviceConnectionState(boolean enable) {
+        return updateWiredDeviceConnectionState(OUTPUT, enable);
+    }
 
     /**
      * @Override
@@ -249,8 +337,8 @@
         return "UsbAlsaDevice: [card: " + mCardNum
             + ", device: " + mDeviceNum
             + ", name: " + mDeviceName
-            + ", hasOutput: " + mHasOutput
-            + ", hasInput: " + mHasInput + "]";
+            + ", hasOutput: " + mHasDevice[OUTPUT]
+            + ", hasInput: " + mHasDevice[INPUT] + "]";
     }
 
     /**
@@ -262,8 +350,8 @@
         dump.write("card", UsbAlsaDeviceProto.CARD, mCardNum);
         dump.write("device", UsbAlsaDeviceProto.DEVICE, mDeviceNum);
         dump.write("name", UsbAlsaDeviceProto.NAME, mDeviceName);
-        dump.write("has_output", UsbAlsaDeviceProto.HAS_PLAYBACK, mHasOutput);
-        dump.write("has_input", UsbAlsaDeviceProto.HAS_CAPTURE, mHasInput);
+        dump.write("has_output", UsbAlsaDeviceProto.HAS_PLAYBACK, mHasDevice[OUTPUT]);
+        dump.write("has_input", UsbAlsaDeviceProto.HAS_CAPTURE, mHasDevice[INPUT]);
         dump.write("address", UsbAlsaDeviceProto.ADDRESS, mDeviceAddress);
 
         dump.end(token);
@@ -294,10 +382,8 @@
         UsbAlsaDevice other = (UsbAlsaDevice) obj;
         return (mCardNum == other.mCardNum
                 && mDeviceNum == other.mDeviceNum
-                && mHasOutput == other.mHasOutput
-                && mHasInput == other.mHasInput
-                && mIsInputHeadset == other.mIsInputHeadset
-                && mIsOutputHeadset == other.mIsOutputHeadset
+                && Arrays.equals(mHasDevice, other.mHasDevice)
+                && Arrays.equals(mIsHeadset, other.mIsHeadset)
                 && mIsDock == other.mIsDock);
     }
 
@@ -310,10 +396,10 @@
         int result = 1;
         result = prime * result + mCardNum;
         result = prime * result + mDeviceNum;
-        result = prime * result + (mHasOutput ? 0 : 1);
-        result = prime * result + (mHasInput ? 0 : 1);
-        result = prime * result + (mIsInputHeadset ? 0 : 1);
-        result = prime * result + (mIsOutputHeadset ? 0 : 1);
+        result = prime * result + (mHasDevice[OUTPUT] ? 0 : 1);
+        result = prime * result + (mHasDevice[INPUT] ? 0 : 1);
+        result = prime * result + (mIsHeadset[INPUT] ? 0 : 1);
+        result = prime * result + (mIsHeadset[OUTPUT] ? 0 : 1);
         result = prime * result + (mIsDock ? 0 : 1);
 
         return result;
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java b/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java
index c498847..d4f0b59 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java
@@ -81,7 +81,8 @@
             if (mStopJackDetect) {
                 return false;
             }
-            mAlsaDevice.updateWiredDeviceConnectionState(true);
+            mAlsaDevice.updateOutputWiredDeviceConnectionState(true);
+            mAlsaDevice.updateInputWiredDeviceConnectionState(true);
         }
         return true;
     }
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index aa1d556..99881e1 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -20,12 +20,14 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.hardware.usb.UsbDevice;
+import android.media.AudioManager;
 import android.media.IAudioService;
 import android.media.midi.MidiDeviceInfo;
 import android.os.Bundle;
 import android.os.FileObserver;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.provider.Settings;
 import android.service.usb.UsbAlsaManagerProto;
 import android.util.Slog;
@@ -42,6 +44,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Stack;
 
 /**
  * UsbAlsaManager manages USB audio and MIDI devices.
@@ -51,8 +54,9 @@
     private static final boolean DEBUG = false;
 
     // Flag to turn on/off multi-peripheral select mode
-    // Set to true to have single-device-only mode
-    private static final boolean mIsSingleMode = true;
+    // Set to true to have multi-devices mode
+    private static final boolean IS_MULTI_MODE = SystemProperties.getBoolean(
+            "ro.audio.multi_usb_mode", false /*def*/);
 
     private static final String ALSA_DIRECTORY = "/dev/snd/";
 
@@ -70,7 +74,11 @@
     // this is needed to map USB devices to ALSA Audio Devices, especially to remove an
     // ALSA device when we are notified that its associated USB device has been removed.
     private final ArrayList<UsbAlsaDevice> mAlsaDevices = new ArrayList<UsbAlsaDevice>();
-    private UsbAlsaDevice mSelectedDevice;
+    // A map from device type to attached devices. Given the audio framework only supports
+    // single device connection per device type, only the last attached device will be
+    // connected to audio framework. Once the last device is removed, previous device can
+    // be connected to audio framework.
+    private HashMap<Integer, Stack<UsbAlsaDevice>> mAttachedDevices = new HashMap<>();
 
     //
     // Device Denylist
@@ -162,11 +170,6 @@
             Slog.d(TAG, "selectAlsaDevice() " + alsaDevice);
         }
 
-        // This must be where an existing USB audio device is deselected.... (I think)
-        if (mIsSingleMode && mSelectedDevice != null) {
-            deselectAlsaDevice();
-        }
-
         // FIXME Does not yet handle the case where the setting is changed
         // after device connection.  Ideally we should handle the settings change
         // in SettingsObserver. Here we should log that a USB device is connected
@@ -178,21 +181,18 @@
             return;
         }
 
-        mSelectedDevice = alsaDevice;
         alsaDevice.start();
+
         if (DEBUG) {
             Slog.d(TAG, "selectAlsaDevice() - done.");
         }
     }
 
-    private synchronized void deselectAlsaDevice() {
+    private synchronized void deselectAlsaDevice(UsbAlsaDevice selectedDevice) {
         if (DEBUG) {
-            Slog.d(TAG, "deselectAlsaDevice() mSelectedDevice " + mSelectedDevice);
+            Slog.d(TAG, "deselectAlsaDevice() selectedDevice " + selectedDevice);
         }
-        if (mSelectedDevice != null) {
-            mSelectedDevice.stop();
-            mSelectedDevice = null;
-        }
+        selectedDevice.stop();
     }
 
     private int getAlsaDeviceListIndexFor(String deviceAddress) {
@@ -204,32 +204,86 @@
         return -1;
     }
 
-    private UsbAlsaDevice removeAlsaDeviceFromList(String deviceAddress) {
+    private void addDeviceToAttachedDevicesMap(int deviceType, UsbAlsaDevice device) {
+        if (deviceType == AudioManager.DEVICE_NONE) {
+            Slog.i(TAG, "Ignore caching device as the type is NONE, device=" + device);
+            return;
+        }
+        Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
+        if (devices == null) {
+            mAttachedDevices.put(deviceType, new Stack<>());
+            devices = mAttachedDevices.get(deviceType);
+        }
+        devices.push(device);
+    }
+
+    private void addAlsaDevice(UsbAlsaDevice device) {
+        mAlsaDevices.add(0, device);
+        addDeviceToAttachedDevicesMap(device.getInputDeviceType(), device);
+        addDeviceToAttachedDevicesMap(device.getOutputDeviceType(), device);
+    }
+
+    private void removeDeviceFromAttachedDevicesMap(int deviceType, UsbAlsaDevice device) {
+        Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
+        if (devices == null) {
+            return;
+        }
+        devices.remove(device);
+        if (devices.isEmpty()) {
+            mAttachedDevices.remove(deviceType);
+        }
+    }
+
+    private UsbAlsaDevice removeAlsaDevice(String deviceAddress) {
         int index = getAlsaDeviceListIndexFor(deviceAddress);
         if (index > -1) {
-            return mAlsaDevices.remove(index);
+            UsbAlsaDevice device = mAlsaDevices.remove(index);
+            removeDeviceFromAttachedDevicesMap(device.getOutputDeviceType(), device);
+            removeDeviceFromAttachedDevicesMap(device.getInputDeviceType(), device);
+            return device;
         } else {
             return null;
         }
     }
 
-    /* package */ UsbAlsaDevice selectDefaultDevice() {
+    private UsbAlsaDevice selectDefaultDevice(int deviceType) {
         if (DEBUG) {
-            Slog.d(TAG, "selectDefaultDevice()");
+            Slog.d(TAG, "selectDefaultDevice():" + deviceType);
         }
 
-        if (mAlsaDevices.size() > 0) {
-            UsbAlsaDevice alsaDevice = mAlsaDevices.get(0);
-            if (DEBUG) {
-                Slog.d(TAG, "  alsaDevice:" + alsaDevice);
-            }
-            if (alsaDevice != null) {
-                selectAlsaDevice(alsaDevice);
-            }
-            return alsaDevice;
-        } else {
+        Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
+        if (devices == null || devices.isEmpty()) {
             return null;
         }
+        UsbAlsaDevice alsaDevice = devices.peek();
+        Slog.d(TAG, "select default device:" + alsaDevice);
+        if (AudioManager.isInputDevice(deviceType)) {
+            alsaDevice.startInput();
+        } else {
+            alsaDevice.startOutput();
+        }
+        return alsaDevice;
+    }
+
+    private void deselectCurrentDevice(int deviceType) {
+        if (DEBUG) {
+            Slog.d(TAG, "deselectCurrentDevice():" + deviceType);
+        }
+        if (deviceType == AudioManager.DEVICE_NONE) {
+            return;
+        }
+
+        Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
+        if (devices == null || devices.isEmpty()) {
+            return;
+        }
+        UsbAlsaDevice alsaDevice = devices.peek();
+        Slog.d(TAG, "deselect current device:" + alsaDevice);
+        if (AudioManager.isInputDevice(deviceType)) {
+            alsaDevice.stopInput();
+        } else {
+            alsaDevice.stopOutput();
+        }
     }
 
     /* package */ void usbDeviceAdded(String deviceAddress, UsbDevice usbDevice,
@@ -246,6 +300,7 @@
         AlsaCardsParser.AlsaCardRecord cardRec =
                 mCardsParser.findCardNumFor(deviceAddress);
         if (cardRec == null) {
+            Slog.e(TAG, "usbDeviceAdded(): cannot find sound card for " + deviceAddress);
             return;
         }
 
@@ -275,12 +330,19 @@
                     new UsbAlsaDevice(mAudioService, cardRec.getCardNum(), 0 /*device*/,
                                       deviceAddress, hasOutput, hasInput,
                                       isInputHeadset, isOutputHeadset, isDock);
-            if (alsaDevice != null) {
-                alsaDevice.setDeviceNameAndDescription(
-                          cardRec.getCardName(), cardRec.getCardDescription());
-                mAlsaDevices.add(0, alsaDevice);
-                selectAlsaDevice(alsaDevice);
+            alsaDevice.setDeviceNameAndDescription(
+                      cardRec.getCardName(), cardRec.getCardDescription());
+            if (IS_MULTI_MODE) {
+                deselectCurrentDevice(alsaDevice.getInputDeviceType());
+                deselectCurrentDevice(alsaDevice.getOutputDeviceType());
+            } else {
+                // At single mode, the first device is the selected device.
+                if (!mAlsaDevices.isEmpty()) {
+                    deselectAlsaDevice(mAlsaDevices.get(0));
+                }
             }
+            addAlsaDevice(alsaDevice);
+            selectAlsaDevice(alsaDevice);
         }
 
         addMidiDevice(deviceAddress, usbDevice, parser, cardRec);
@@ -346,12 +408,20 @@
         }
 
         // Audio
-        UsbAlsaDevice alsaDevice = removeAlsaDeviceFromList(deviceAddress);
+        UsbAlsaDevice alsaDevice = removeAlsaDevice(deviceAddress);
         Slog.i(TAG, "USB Audio Device Removed: " + alsaDevice);
-        if (alsaDevice != null && alsaDevice == mSelectedDevice) {
+        if (alsaDevice != null) {
             waitForAlsaDevice(alsaDevice.getCardNum(), false /*isAdded*/);
-            deselectAlsaDevice();
-            selectDefaultDevice(); // if there any external devices left, select one of them
+            deselectAlsaDevice(alsaDevice);
+            if (IS_MULTI_MODE) {
+                selectDefaultDevice(alsaDevice.getOutputDeviceType());
+                selectDefaultDevice(alsaDevice.getInputDeviceType());
+            } else {
+                // If there are any external devices left, select the latest attached one
+                if (!mAlsaDevices.isEmpty() && mAlsaDevices.get(0) != null) {
+                    selectAlsaDevice(mAlsaDevices.get(0));
+                }
+            }
         }
 
         // MIDI
@@ -362,7 +432,6 @@
         }
 
         logDevices("usbDeviceRemoved()");
-
     }
 
    /* package */ void setPeripheralMidiState(boolean enabled, int card, int device) {
diff --git a/services/voiceinteraction/TEST_MAPPING b/services/voiceinteraction/TEST_MAPPING
index 5fe1c8d..f098155 100644
--- a/services/voiceinteraction/TEST_MAPPING
+++ b/services/voiceinteraction/TEST_MAPPING
@@ -5,6 +5,9 @@
       "options": [
         {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceStressTest"
         }
       ]
     },
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 5efd158..07dc1c6 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -315,12 +315,13 @@
             IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig,
             int keyphraseId, boolean runInBatterySaverMode) {
         synchronized (mLock) {
+            // TODO Remove previous callback handling
             IRecognitionStatusCallback oldCallback = modelData.getCallback();
             if (oldCallback != null && oldCallback.asBinder() != callback.asBinder()) {
                 Slog.w(TAG, "Canceling previous recognition for model id: "
                         + modelData.getModelId());
                 try {
-                    oldCallback.onError(STATUS_ERROR);
+                    oldCallback.onPreempted();
                 } catch (RemoteException e) {
                     Slog.w(TAG, "RemoteException in onDetectionStopped", e);
                 }
@@ -759,15 +760,12 @@
                     onRecognitionAbortLocked(event);
                     break;
                 case SoundTrigger.RECOGNITION_STATUS_FAILURE:
-                    // Fire failures to all listeners since it's not tied to a keyphrase.
-                    onRecognitionFailureLocked();
-                    break;
                 case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
                 case SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE:
                     if (isKeyphraseRecognitionEvent(event)) {
-                        onKeyphraseRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
+                        onKeyphraseRecognitionLocked((KeyphraseRecognitionEvent) event);
                     } else {
-                        onGenericRecognitionSuccessLocked((GenericRecognitionEvent) event);
+                        onGenericRecognitionLocked((GenericRecognitionEvent) event);
                     }
                     break;
             }
@@ -778,7 +776,7 @@
         return event instanceof KeyphraseRecognitionEvent;
     }
 
-    private void onGenericRecognitionSuccessLocked(GenericRecognitionEvent event) {
+    private void onGenericRecognitionLocked(GenericRecognitionEvent event) {
         MetricsLogger.count(mContext, "sth_generic_recognition_event", 1);
         if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS
                 && event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
@@ -901,17 +899,6 @@
         }
     }
 
-    private void onRecognitionFailureLocked() {
-        Slog.w(TAG, "Recognition failure");
-        MetricsLogger.count(mContext, "sth_recognition_failure_event", 1);
-        try {
-            sendErrorCallbacksToAllLocked(STATUS_ERROR);
-        } finally {
-            internalClearModelStateLocked();
-            internalClearGlobalStateLocked();
-        }
-    }
-
     private int getKeyphraseIdFromEvent(KeyphraseRecognitionEvent event) {
         if (event == null) {
             Slog.w(TAG, "Null RecognitionEvent received.");
@@ -927,7 +914,7 @@
         return keyphraseExtras[0].id;
     }
 
-    private void onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
+    private void onKeyphraseRecognitionLocked(KeyphraseRecognitionEvent event) {
         Slog.i(TAG, "Recognition success");
         MetricsLogger.count(mContext, "sth_keyphrase_recognition_event", 1);
         int keyphraseId = getKeyphraseIdFromEvent(event);
@@ -1001,7 +988,17 @@
     private void onServiceDiedLocked() {
         try {
             MetricsLogger.count(mContext, "sth_service_died", 1);
-            sendErrorCallbacksToAllLocked(SoundTrigger.STATUS_DEAD_OBJECT);
+            for (ModelData modelData : mModelDataMap.values()) {
+                IRecognitionStatusCallback callback = modelData.getCallback();
+                if (callback != null) {
+                    try {
+                        callback.onModuleDied();
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "RemoteException send moduleDied for model handle " +
+                                modelData.getHandle(), e);
+                    }
+                }
+            }
         } finally {
             internalClearModelStateLocked();
             internalClearGlobalStateLocked();
@@ -1111,21 +1108,6 @@
         }
     }
 
-    // Sends an error callback to all models with a valid registered callback.
-    private void sendErrorCallbacksToAllLocked(int errorCode) {
-        for (ModelData modelData : mModelDataMap.values()) {
-            IRecognitionStatusCallback callback = modelData.getCallback();
-            if (callback != null) {
-                try {
-                    callback.onError(errorCode);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException sendErrorCallbacksToAllLocked for model handle " +
-                            modelData.getHandle(), e);
-                }
-            }
-        }
-    }
-
     /**
      * Stops and unloads all models. This is intended as a clean-up call with the expectation that
      * this instance is not used after.
@@ -1342,11 +1324,11 @@
             // Notify of error if needed.
             if (notifyClientOnError) {
                 try {
-                    callback.onError(status);
+                    callback.onResumeFailed(status);
                 } catch (DeadObjectException e) {
                     forceStopAndUnloadModelLocked(modelData, e);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException in onError", e);
+                    Slog.w(TAG, "RemoteException in onResumeFailed", e);
                 }
             }
         } else {
@@ -1382,15 +1364,15 @@
         status = mModule.stopRecognition(modelData.getHandle());
 
         if (status != SoundTrigger.STATUS_OK) {
-            Slog.w(TAG, "stopRecognition call failed with " + status);
+            Slog.e(TAG, "stopRecognition call failed with " + status);
             MetricsLogger.count(mContext, "sth_stop_recognition_error", 1);
             if (notify) {
                 try {
-                    callback.onError(status);
+                    callback.onPauseFailed(status);
                 } catch (DeadObjectException e) {
                     forceStopAndUnloadModelLocked(modelData, e);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException in onError", e);
+                    Slog.w(TAG, "RemoteException in onPauseFailed", e);
                 }
             }
         } else {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 203a3e7..1bbea89 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -25,6 +25,7 @@
 import static android.content.pm.PackageManager.GET_SERVICES;
 import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
 import static android.hardware.soundtrigger.SoundTrigger.STATUS_BAD_VALUE;
+import static android.hardware.soundtrigger.SoundTrigger.STATUS_DEAD_OBJECT;
 import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
 import static android.hardware.soundtrigger.SoundTrigger.STATUS_OK;
 import static android.provider.Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY;
@@ -1388,8 +1389,7 @@
                         }));
             }
 
-            @Override
-            public void onError(int status) {
+            private void onError(int status) {
                 if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
 
                 sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
@@ -1412,6 +1412,30 @@
             }
 
             @Override
+            public void onPreempted() {
+                if (DEBUG) Slog.v(TAG, mPuuid + ": onPreempted");
+                onError(STATUS_ERROR);
+            }
+
+            @Override
+            public void onModuleDied() {
+                if (DEBUG) Slog.v(TAG, mPuuid + ": onModuleDied");
+                onError(STATUS_DEAD_OBJECT);
+            }
+
+            @Override
+            public void onResumeFailed(int status) {
+                if (DEBUG) Slog.v(TAG, mPuuid + ": onResumeFailed: " + status);
+                onError(status);
+            }
+
+            @Override
+            public void onPauseFailed(int status) {
+                if (DEBUG) Slog.v(TAG, mPuuid + ": onPauseFailed: " + status);
+                onError(status);
+            }
+
+            @Override
             public void onRecognitionPaused() {
                 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index f3cb9ba..a1adee7 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -56,6 +56,7 @@
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
 import android.service.voice.ISandboxedDetectionService;
 import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
+import android.service.voice.SoundTriggerFailure;
 import android.service.voice.VisualQueryDetectionService;
 import android.service.voice.VisualQueryDetectionServiceFailure;
 import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
@@ -576,8 +577,31 @@
         }
 
         @Override
-        public void onError(int status) throws RemoteException {
-            mExternalCallback.onError(status);
+        public void onPreempted() throws RemoteException {
+            mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
+                        SoundTriggerFailure.ERROR_CODE_UNEXPECTED_PREEMPTION,
+                        "Unexpected startRecognition on already started ST session"));
+        }
+
+        @Override
+        public void onModuleDied() throws RemoteException {
+            mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
+                        SoundTriggerFailure.ERROR_CODE_MODULE_DIED,
+                        "STHAL died"));
+        }
+
+        @Override
+        public void onResumeFailed(int status) throws RemoteException {
+            mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
+                        SoundTriggerFailure.ERROR_CODE_RECOGNITION_RESUME_FAILED,
+                        "STService recognition resume failed with: " + status));
+        }
+
+        @Override
+        public void onPauseFailed(int status) throws RemoteException {
+            mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
+                        SoundTriggerFailure.ERROR_CODE_RECOGNITION_RESUME_FAILED,
+                        "STService recognition pause failed with: " + status));
         }
 
         @Override
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 78c6196..559faf9 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -3142,10 +3142,10 @@
      *
      * Only supported for embedded subscriptions (if {@link SubscriptionInfo#isEmbedded} returns
      * true). To check for permissions for non-embedded subscription as well,
+     * see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}.
      *
      * @param info The subscription to check.
      * @return whether the app is authorized to manage this subscription per its metadata.
-     *
      * @see android.telephony.TelephonyManager#hasCarrierPrivileges
      */
     public boolean canManageSubscription(SubscriptionInfo info) {
@@ -3159,12 +3159,12 @@
      *
      * Only supported for embedded subscriptions (if {@link SubscriptionInfo#isEmbedded} returns
      * true). To check for permissions for non-embedded subscription as well,
+     * see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}.
      *
      * @param info The subscription to check.
      * @param packageName Package name of the app to check.
      *
      * @return whether the app is authorized to manage this subscription per its access rules.
-     *
      * @see android.telephony.TelephonyManager#hasCarrierPrivileges
      * @hide
      */
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
index f2ffc19..7272abb 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -29,6 +29,7 @@
         <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
         <option name="teardown-command" value="settings delete system show_touches" />
         <option name="teardown-command" value="settings delete system pointer_location" />
+        <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index f8d885a..d7fa124 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -417,9 +417,9 @@
                         int failureReason, int mitigationCount) {
                     if (versionedPackage.getVersionCode() == VERSION_CODE) {
                         // Only rollback for specific versionCode
-                        return PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
+                        return PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
                     }
-                    return PackageHealthObserverImpact.USER_IMPACT_NONE;
+                    return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
                 }
             };
 
@@ -442,13 +442,13 @@
     public void testPackageFailureNotifyAllDifferentImpacts() throws Exception {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observerNone = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_NONE);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
         TestObserver observerHigh = new TestObserver(OBSERVER_NAME_2,
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
         TestObserver observerMid = new TestObserver(OBSERVER_NAME_3,
-                PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
         TestObserver observerLow = new TestObserver(OBSERVER_NAME_4,
-                PackageHealthObserverImpact.USER_IMPACT_LOW);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
 
         // Start observing for all impact observers
         watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
@@ -499,9 +499,9 @@
     public void testPackageFailureNotifyLeastImpactSuccessively() throws Exception {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observerFirst = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_LOW);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
         TestObserver observerSecond = new TestObserver(OBSERVER_NAME_2,
-                PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
 
         // Start observing for observerFirst and observerSecond with failure handling
         watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
@@ -517,7 +517,7 @@
         assertThat(observerSecond.mMitigatedPackages).isEmpty();
 
         // After observerFirst handles failure, next action it has is high impact
-        observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_HIGH;
+        observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_100;
         observerFirst.mMitigatedPackages.clear();
         observerSecond.mMitigatedPackages.clear();
 
@@ -531,7 +531,7 @@
         assertThat(observerFirst.mMitigatedPackages).isEmpty();
 
         // After observerSecond handles failure, it has no further actions
-        observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE;
+        observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
         observerFirst.mMitigatedPackages.clear();
         observerSecond.mMitigatedPackages.clear();
 
@@ -545,7 +545,7 @@
         assertThat(observerSecond.mMitigatedPackages).isEmpty();
 
         // After observerFirst handles failure, it too has no further actions
-        observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE;
+        observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
         observerFirst.mMitigatedPackages.clear();
         observerSecond.mMitigatedPackages.clear();
 
@@ -566,9 +566,9 @@
     public void testPackageFailureNotifyOneSameImpact() throws Exception {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2,
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
 
         // Start observing for observer1 and observer2 with failure handling
         watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
@@ -592,11 +592,11 @@
         TestController controller = new TestController();
         PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2,
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
         TestObserver observer3 = new TestObserver(OBSERVER_NAME_3,
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
 
 
         // Start observing with explicit health checks for APP_A and APP_B respectively
@@ -645,7 +645,7 @@
         TestController controller = new TestController();
         PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
 
         // Start observing with explicit health checks for APP_A and APP_B
         controller.setSupportedPackages(Arrays.asList(APP_A, APP_B, APP_C));
@@ -711,7 +711,7 @@
         TestController controller = new TestController();
         PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
 
         // Start observing with explicit health checks for APP_A and
         // package observation duration == LONG_DURATION
@@ -742,7 +742,7 @@
         TestController controller = new TestController();
         PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
 
         // Start observing with explicit health checks for APP_A and
         // package observation duration == SHORT_DURATION / 2
@@ -818,7 +818,7 @@
 
         // Start observing with failure handling
         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
         wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION);
 
         // Notify of NetworkStack failure
@@ -1073,9 +1073,9 @@
     public void testBootLoopMitigationDoneForLowestUserImpact() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver bootObserver1 = new TestObserver(OBSERVER_NAME_1);
-        bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LOW);
+        bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
         TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
-        bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+        bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
         watchdog.registerHealthObserver(bootObserver1);
         watchdog.registerHealthObserver(bootObserver2);
         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
@@ -1446,7 +1446,7 @@
 
         TestObserver(String name) {
             mName = name;
-            mImpact = PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
+            mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
         }
 
         TestObserver(String name, int impact) {
diff --git a/tests/testables/tests/AndroidManifest.xml b/tests/testables/tests/AndroidManifest.xml
index 2bfb04f..1731f6b 100644
--- a/tests/testables/tests/AndroidManifest.xml
+++ b/tests/testables/tests/AndroidManifest.xml
@@ -21,7 +21,7 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.MANAGE_USERS" />
 
-    <application android:debuggable="true">
+    <application android:debuggable="true" android:testOnly="true">
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/tests/testables/tests/AndroidTest.xml b/tests/testables/tests/AndroidTest.xml
new file mode 100644
index 0000000..6d29794
--- /dev/null
+++ b/tests/testables/tests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Runs Testable Tests.">
+    <option name="test-tag" value="TestablesTests" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="TestablesTests.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.testables"/>
+    </test>
+</configuration>