Merge "CSD: Implement logic for initial safe hearing warning"
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java
index 9ada8dc..47b7e13 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java
@@ -24,6 +24,7 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.server.JobSchedulerBackgroundThread;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateControllerProto;
@@ -50,7 +51,7 @@
     public static final String ACTION_UNFORCE_IDLE = "com.android.server.jobscheduler.UNFORCE_IDLE";
 
     // After construction, mutations of idle/screen-on state will only happen
-    // on the main looper thread, either in onReceive() or in an alarm callback.
+    // on the JobScheduler thread, either in onReceive() or in an alarm callback.
     private boolean mIdle;
     private boolean mGarageModeOn;
     private boolean mForced;
@@ -90,7 +91,7 @@
         filter.addAction(ACTION_UNFORCE_IDLE);
         filter.addAction(ActivityManagerService.ACTION_TRIGGER_IDLE);
 
-        context.registerReceiver(this, filter);
+        context.registerReceiver(this, filter, null, JobSchedulerBackgroundThread.getHandler());
     }
 
     @Override
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java
index 140cca6..15d6766 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java
@@ -31,6 +31,7 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.server.JobSchedulerBackgroundThread;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateControllerProto;
@@ -47,8 +48,8 @@
     private AlarmManager mAlarm;
     private PowerManager mPowerManager;
 
-    // After construction, mutations of idle/screen-on state will only happen
-    // on the main looper thread, either in onReceive() or in an alarm callback.
+    // After construction, mutations of idle/screen-on/projection states will only happen
+    // on the JobScheduler thread, either in onReceive(), in an alarm callback, or in on.*Changed.
     private long mInactivityIdleThreshold;
     private long mIdleWindowSlop;
     private boolean mIdle;
@@ -101,12 +102,10 @@
         filter.addAction(Intent.ACTION_DOCK_IDLE);
         filter.addAction(Intent.ACTION_DOCK_ACTIVE);
 
-        context.registerReceiver(this, filter);
+        context.registerReceiver(this, filter, null, JobSchedulerBackgroundThread.getHandler());
 
-        // TODO(b/172579710): Move the callbacks off the main executor and on to
-        //  JobSchedulerBackgroundThread.getExecutor() once synchronization is fixed in this class.
         context.getSystemService(UiModeManager.class).addOnProjectionStateChangedListener(
-                UiModeManager.PROJECTION_TYPE_ALL, context.getMainExecutor(),
+                UiModeManager.PROJECTION_TYPE_ALL, JobSchedulerBackgroundThread.getExecutor(),
                 mOnProjectionStateChangedListener);
     }
 
@@ -226,7 +225,8 @@
                 Slog.v(TAG, "Scheduling idle : " + reason + " now:" + nowElapsed + " when=" + when);
             }
             mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                    when, mIdleWindowSlop, "JS idleness", mIdleAlarmListener, null);
+                    when, mIdleWindowSlop, "JS idleness",
+                    JobSchedulerBackgroundThread.getExecutor(), mIdleAlarmListener);
         }
     }
 
diff --git a/core/api/current.txt b/core/api/current.txt
index 21e358f..cbcdac5 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9574,8 +9574,6 @@
 
   public final class VirtualDeviceManager {
     method @NonNull public java.util.List<android.companion.virtual.VirtualDevice> getVirtualDevices();
-    field public static final int DEVICE_ID_DEFAULT = 0; // 0x0
-    field public static final int DEVICE_ID_INVALID = -1; // 0xffffffff
   }
 
 }
@@ -10352,6 +10350,8 @@
     field public static final int CONTEXT_RESTRICTED = 4; // 0x4
     field public static final String CREDENTIAL_SERVICE = "credential";
     field public static final String CROSS_PROFILE_APPS_SERVICE = "crossprofileapps";
+    field public static final int DEVICE_ID_DEFAULT = 0; // 0x0
+    field public static final int DEVICE_ID_INVALID = -1; // 0xffffffff
     field public static final String DEVICE_LOCK_SERVICE = "device_lock";
     field public static final String DEVICE_POLICY_SERVICE = "device_policy";
     field public static final String DISPLAY_HASH_SERVICE = "display_hash";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 23e3dce..6e6ec44 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2181,6 +2181,7 @@
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createRestrictedProfile(@Nullable String);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createUser(@Nullable String, @NonNull String, int);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle getBootUser();
+    method public int getDisplayIdAssignedToUser();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.Set<java.lang.String> getPreInstallableSystemPackages(@NonNull String);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType();
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
@@ -3548,6 +3549,8 @@
   public final class InputMethodInfo implements android.os.Parcelable {
     ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, boolean, @NonNull String);
     ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, int);
+    field public static final int COMPONENT_NAME_MAX_LENGTH = 1000; // 0x3e8
+    field public static final int MAX_IMES_PER_PACKAGE = 20; // 0x14
   }
 
   public final class InputMethodManager {
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 6422865..3615435 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -198,6 +198,26 @@
  * possible for a node to contain outdated information because the window content may change at any
  * time.
  * </p>
+ * <h3>Drawing Accessibility Overlays</h3>
+ * <p>Accessibility services can draw overlays on top of existing screen contents.
+ * Accessibility overlays can be used to visually highlight items on the screen
+ * e.g. indicate the current item with accessibility focus.
+ * Overlays can also offer the user a way to interact with the service directly and quickly
+ * customize the service's behavior.</p>
+ * <p>Accessibility overlays can be attached to a particular window or to the display itself.
+ * Attaching an overlay to a window allows the overly to move, grow and shrink as the window does.
+ * The overlay will maintain the same relative position within the window bounds as the window
+ * moves. The overlay will also maintain the same relative position within the window bounds if
+ * the window is resized.
+ * To attach an overlay to a window, use {@link attachAccessibilityOverlayToWindow}.
+ * Attaching an overlay to the display means that the overlay is independent of the active
+ * windows on that display.
+ * To attach an overlay to a display, use {@link attachAccessibilityOverlayToDisplay}. </p>
+ * <p> When positioning an overlay that is attached to a window, the service must use window
+ * coordinates. In order to position an overlay on top of an existing UI element it is necessary
+ * to know the bounds of that element in window coordinates. To find the bounds in window
+ * coordinates of an element, find the corresponding {@link AccessibilityNodeInfo} as discussed
+ * above and call {@link AccessibilityNodeInfo#getBoundsInWindow}. </p>
  * <h3>Notification strategy</h3>
  * <p>
  * All accessibility services are notified of all events they have requested, regardless of their
@@ -3421,22 +3441,28 @@
     }
 
     /**
-     * Attaches a {@link android.view.SurfaceControl} containing an accessibility
+     * <p>Attaches a {@link android.view.SurfaceControl} containing an accessibility
      * overlay to the
      * specified display. This type of overlay should be used for content that does
      * not need to
      * track the location and size of Views in the currently active app e.g. service
      * configuration
-     * or general service UI. To remove this overlay and free the associated
+     * or general service UI.</p>
+     * <p>Generally speaking, an accessibility overlay  will be  a {@link android.view.View}.
+     * To embed the View into a {@link android.view.SurfaceControl}, create a
+     * {@link android.view.SurfaceControlViewHost} and attach the View using
+     * {@link android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by
+     * calling <code> viewHost.getSurfacePackage().getSurfaceControl()</code>.</p>
+     * <p>To remove this overlay and free the associated
      * resources, use
-     * <code> new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.
-     * If the specified overlay has already been attached to the specified display
+     * <code> new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.</p>
+     * <p>If the specified overlay has already been attached to the specified display
      * this method does nothing.
      * If the specified overlay has already been attached to a previous display this
      * function will transfer the overlay to the new display.
      * Services can attach multiple overlays. Use
      * <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>.
-     * to coordinate the order of the overlays on screen.
+     * to coordinate the order of the overlays on screen.</p>
      *
      * @param displayId the display to which the SurfaceControl should be attached.
      * @param sc        the SurfaceControl containing the overlay content
@@ -3456,20 +3482,24 @@
     }
 
     /**
-     * Attaches an accessibility overlay {@link android.view.SurfaceControl} to the
+     * <p>Attaches an accessibility overlay {@link android.view.SurfaceControl} to the
      * specified
      * window. This method should be used when you want the overlay to move and
-     * resize as the parent
-     * window moves and resizes. To remove this overlay and free the associated
-     * resources, use
-     * <code> new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.
-     * If the specified overlay has already been attached to the specified window
+     * resize as the parent window moves and resizes.</p>
+     * <p>Generally speaking, an accessibility overlay  will be  a {@link android.view.View}.
+     * To embed the View into a {@link android.view.SurfaceControl}, create a
+     * {@link android.view.SurfaceControlViewHost} and attach the View using
+     * {@link android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by
+     * calling <code> viewHost.getSurfacePackage().getSurfaceControl()</code>.</p>
+     * <p>To remove this overlay and free the associated resources, use
+     * <code> new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.</p>
+     * <p>If the specified overlay has already been attached to the specified window
      * this method does nothing.
      * If the specified overlay has already been attached to a previous window this
      * function will transfer the overlay to the new window.
      * Services can attach multiple overlays. Use
      * <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>.
-     * to coordinate the order of the overlays on screen.
+     * to coordinate the order of the overlays on screen.</p>
      *
      * @param accessibilityWindowId The window id, from
      *                              {@link AccessibilityWindowInfo#getId()}.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b50245d..b0929b5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4621,7 +4621,7 @@
                     ActivityManager.getService());
             if (!service.isUiContext()) { // WindowProviderService is a UI Context.
                 VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
-                if (mLastReportedDeviceId == VirtualDeviceManager.DEVICE_ID_DEFAULT
+                if (mLastReportedDeviceId == Context.DEVICE_ID_DEFAULT
                         || vdm.isValidVirtualDeviceId(mLastReportedDeviceId)) {
                     service.updateDeviceId(mLastReportedDeviceId);
                 }
@@ -6149,7 +6149,7 @@
 
     private void updateDeviceIdForNonUIContexts(int deviceId) {
         // Invalid device id is treated as a no-op.
-        if (deviceId == VirtualDeviceManager.DEVICE_ID_INVALID) {
+        if (deviceId == Context.DEVICE_ID_INVALID) {
             return;
         }
         if (deviceId == mLastReportedDeviceId) {
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index fe40a4c..f48181b 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -60,7 +60,6 @@
     private String[] mRequireNoneOfPermissions;
     private long mRequireCompatChangeId = CHANGE_INVALID;
     private long mIdForResponseEvent;
-    private @Nullable IntentFilter mRemoveMatchingFilter;
     private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
     private @Nullable String mDeliveryGroupMatchingKey;
     private @Nullable BundleMerger mDeliveryGroupExtrasMerger;
@@ -190,12 +189,6 @@
             "android:broadcast.idForResponseEvent";
 
     /**
-     * Corresponds to {@link #setRemoveMatchingFilter}.
-     */
-    private static final String KEY_REMOVE_MATCHING_FILTER =
-            "android:broadcast.removeMatchingFilter";
-
-    /**
      * Corresponds to {@link #setDeliveryGroupPolicy(int)}.
      */
     private static final String KEY_DELIVERY_GROUP_POLICY =
@@ -274,18 +267,6 @@
     }
 
     /**
-     * {@hide}
-     * @deprecated use {@link #setDeliveryGroupMatchingFilter(IntentFilter)} instead.
-     */
-    @Deprecated
-    public static @NonNull BroadcastOptions makeRemovingMatchingFilter(
-            @NonNull IntentFilter removeMatchingFilter) {
-        BroadcastOptions opts = new BroadcastOptions();
-        opts.setRemoveMatchingFilter(removeMatchingFilter);
-        return opts;
-    }
-
-    /**
      * Creates a new {@code BroadcastOptions} with no options initially set.
      */
     public BroadcastOptions() {
@@ -315,8 +296,6 @@
         mRequireNoneOfPermissions = opts.getStringArray(KEY_REQUIRE_NONE_OF_PERMISSIONS);
         mRequireCompatChangeId = opts.getLong(KEY_REQUIRE_COMPAT_CHANGE_ID, CHANGE_INVALID);
         mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
-        mRemoveMatchingFilter = opts.getParcelable(KEY_REMOVE_MATCHING_FILTER,
-                IntentFilter.class);
         mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
                 DELIVERY_GROUP_POLICY_ALL);
         mDeliveryGroupMatchingKey = opts.getString(KEY_DELIVERY_GROUP_KEY);
@@ -797,31 +776,6 @@
     }
 
     /**
-     * When enqueuing this broadcast, remove all pending broadcasts previously
-     * sent by this app which match the given filter.
-     * <p>
-     * For example, sending {@link Intent#ACTION_SCREEN_ON} would typically want
-     * to remove any pending {@link Intent#ACTION_SCREEN_OFF} broadcasts.
-     *
-     * @hide
-     * @deprecated use {@link #setDeliveryGroupMatchingFilter(IntentFilter)} instead.
-     */
-    @Deprecated
-    public void setRemoveMatchingFilter(@NonNull IntentFilter removeMatchingFilter) {
-        mRemoveMatchingFilter = Objects.requireNonNull(removeMatchingFilter);
-    }
-
-    /** @hide */
-    public void clearRemoveMatchingFilter() {
-        mRemoveMatchingFilter = null;
-    }
-
-    /** @hide */
-    public @Nullable IntentFilter getRemoveMatchingFilter() {
-        return mRemoveMatchingFilter;
-    }
-
-    /**
      * Set delivery group policy for this broadcast to specify how multiple broadcasts belonging to
      * the same delivery group has to be handled.
      *
@@ -1092,9 +1046,6 @@
         if (mIdForResponseEvent != 0) {
             b.putLong(KEY_ID_FOR_RESPONSE_EVENT, mIdForResponseEvent);
         }
-        if (mRemoveMatchingFilter != null) {
-            b.putParcelable(KEY_REMOVE_MATCHING_FILTER, mRemoveMatchingFilter);
-        }
         if (mDeliveryGroupPolicy != DELIVERY_GROUP_POLICY_ALL) {
             b.putInt(KEY_DELIVERY_GROUP_POLICY, mDeliveryGroupPolicy);
         }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 1c0be68..e3ec493 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -245,7 +245,7 @@
     @UnsupportedAppUsage
     private @NonNull Resources mResources;
     private @Nullable Display mDisplay; // may be null if invalid display or not initialized yet.
-    private int mDeviceId = VirtualDeviceManager.DEVICE_ID_DEFAULT;
+    private int mDeviceId = Context.DEVICE_ID_DEFAULT;
 
     /**
      * If set to {@code true} the resources for this context will be configured for mDisplay which
@@ -2812,7 +2812,7 @@
 
     @Override
     public @NonNull Context createDeviceContext(int deviceId) {
-        if (deviceId != VirtualDeviceManager.DEVICE_ID_DEFAULT) {
+        if (deviceId != Context.DEVICE_ID_DEFAULT) {
             VirtualDeviceManager vdm = getSystemService(VirtualDeviceManager.class);
             if (!vdm.isValidVirtualDeviceId(deviceId)) {
                 throw new IllegalArgumentException(
@@ -3092,7 +3092,7 @@
 
     @Override
     public void updateDeviceId(int updatedDeviceId) {
-        if (updatedDeviceId != VirtualDeviceManager.DEVICE_ID_DEFAULT) {
+        if (updatedDeviceId != Context.DEVICE_ID_DEFAULT) {
             VirtualDeviceManager vdm = getSystemService(VirtualDeviceManager.class);
             if (!vdm.isValidVirtualDeviceId(updatedDeviceId)) {
                 throw new IllegalArgumentException(
diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java
index f3f43354..4a09186 100644
--- a/core/java/android/companion/virtual/VirtualDevice.java
+++ b/core/java/android/companion/virtual/VirtualDevice.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -38,9 +39,9 @@
      * @hide
      */
     public VirtualDevice(int id, @Nullable String name) {
-        if (id <= VirtualDeviceManager.DEVICE_ID_DEFAULT) {
+        if (id <= Context.DEVICE_ID_DEFAULT) {
             throw new IllegalArgumentException("VirtualDevice ID mist be greater than "
-                    + VirtualDeviceManager.DEVICE_ID_DEFAULT);
+                    + Context.DEVICE_ID_DEFAULT);
         }
         mId = id;
         mName = name;
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index ae43c6e..4d2c7cb 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -98,16 +98,6 @@
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
 
     /**
-     * The default device ID, which is the ID of the primary (non-virtual) device.
-     */
-    public static final int DEVICE_ID_DEFAULT = 0;
-
-    /**
-     * Invalid device ID.
-     */
-    public static final int DEVICE_ID_INVALID = -1;
-
-    /**
      * Broadcast Action: A Virtual Device was removed.
      *
      * <p class="note">This is a protected intent that can only be sent by the system.</p>
@@ -250,7 +240,7 @@
     public int getDeviceIdForDisplayId(int displayId) {
         if (mService == null) {
             Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service.");
-            return DEVICE_ID_DEFAULT;
+            return Context.DEVICE_ID_DEFAULT;
         }
         try {
             return mService.getDeviceIdForDisplayId(displayId);
@@ -261,7 +251,7 @@
 
     /**
      * Checks whether the passed {@code deviceId} is a valid virtual device ID or not.
-     * {@link VirtualDeviceManager#DEVICE_ID_DEFAULT} is not valid as it is the ID of the default
+     * {@link Context#DEVICE_ID_DEFAULT} is not valid as it is the ID of the default
      * device which is not a virtual device. {@code deviceId} must correspond to a virtual device
      * created by {@link VirtualDeviceManager#createVirtualDevice(int, VirtualDeviceParams)}.
      *
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 4dccc8d..658702f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -135,6 +135,15 @@
     @VisibleForTesting
     public static final long OVERRIDABLE_COMPONENT_CALLBACKS = 193247900L;
 
+    /**
+     * The default device ID, which is the ID of the primary (non-virtual) device.
+     */
+    public static final int DEVICE_ID_DEFAULT = 0;
+    /**
+     * Invalid device ID.
+     */
+    public static final int DEVICE_ID_INVALID = -1;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "MODE_" }, value = {
             MODE_PRIVATE,
@@ -4267,7 +4276,7 @@
      * <p>Note: When implementing this method, keep in mind that new services can be added on newer
      * Android releases, so if you're looking for just the explicit names mentioned above, make sure
      * to return {@code null} when you don't recognize the name &mdash; if you throw a
-     * {@link RuntimeException} exception instead, you're app might break on new Android releases.
+     * {@link RuntimeException} exception instead, your app might break on new Android releases.
      *
      * @param name The name of the desired service.
      *
@@ -7175,7 +7184,7 @@
      * <p>
      * Applications that run on virtual devices may use this method to access the default device
      * capabilities and functionality (by passing
-     * {@link android.companion.virtual.VirtualDeviceManager#DEVICE_ID_DEFAULT}. Similarly,
+     * {@link Context#DEVICE_ID_DEFAULT}. Similarly,
      * applications running on the default device may access the functionality of virtual devices.
      * </p>
      * <p>
@@ -7542,7 +7551,7 @@
      * determine whether they are running on a virtual device and identify that device.
      *
      * The device ID of the host device is
-     * {@link android.companion.virtual.VirtualDeviceManager#DEVICE_ID_DEFAULT}
+     * {@link Context#DEVICE_ID_DEFAULT}
      *
      * <p>
      * If the underlying device ID is changed by the system, for example, when an
diff --git a/core/java/android/content/res/OWNERS b/core/java/android/content/res/OWNERS
index d12d920..a7bce12 100644
--- a/core/java/android/content/res/OWNERS
+++ b/core/java/android/content/res/OWNERS
@@ -4,3 +4,5 @@
 toddke@google.com
 patb@google.com
 zyy@google.com
+
+per-file FontScaleConverter*=fuego@google.com
\ No newline at end of file
diff --git a/core/java/android/credentials/CreateCredentialRequest.java b/core/java/android/credentials/CreateCredentialRequest.java
index b756a43..c89a5c6 100644
--- a/core/java/android/credentials/CreateCredentialRequest.java
+++ b/core/java/android/credentials/CreateCredentialRequest.java
@@ -244,7 +244,7 @@
     /** A builder for {@link CreateCredentialRequest}. */
     public static final class Builder {
 
-        private boolean mAlwaysSendAppInfoToProvider;
+        private boolean mAlwaysSendAppInfoToProvider = true;
 
         @NonNull
         private String mType;
diff --git a/core/java/android/credentials/ui/CancelUiRequest.java b/core/java/android/credentials/ui/CancelUiRequest.java
new file mode 100644
index 0000000..6bd9de4
--- /dev/null
+++ b/core/java/android/credentials/ui/CancelUiRequest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 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.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * A request to cancel any ongoing UI matching this request.
+ *
+ * @hide
+ */
+public final class CancelUiRequest implements Parcelable {
+
+    /**
+     * The intent extra key for the {@code CancelUiRequest} object when launching the UX
+     * activities.
+     */
+    @NonNull public static final String EXTRA_CANCEL_UI_REQUEST =
+            "android.credentials.ui.extra.EXTRA_CANCEL_UI_REQUEST";
+
+    @NonNull
+    private final IBinder mToken;
+
+    /** Returns the request token matching the user request that should be cancelled. */
+    @NonNull
+    public IBinder getToken() {
+        return mToken;
+    }
+
+    public CancelUiRequest(@NonNull IBinder token) {
+        mToken = token;
+    }
+
+    private CancelUiRequest(@NonNull Parcel in) {
+        mToken = in.readStrongBinder();
+        AnnotationValidations.validate(NonNull.class, null, mToken);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStrongBinder(mToken);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull public static final Creator<CancelUiRequest> CREATOR = new Creator<>() {
+        @Override
+        public CancelUiRequest createFromParcel(@NonNull Parcel in) {
+            return new CancelUiRequest(in);
+        }
+
+        @Override
+        public CancelUiRequest[] newArray(int size) {
+            return new CancelUiRequest[size];
+        }
+    };
+}
diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java
index 3c10e81..dcfef56 100644
--- a/core/java/android/credentials/ui/IntentFactory.java
+++ b/core/java/android/credentials/ui/IntentFactory.java
@@ -22,6 +22,7 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.res.Resources;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.ResultReceiver;
 
@@ -66,6 +67,25 @@
     }
 
     /**
+     * Creates an Intent that cancels any UI matching the given request token id.
+     *
+     * @hide
+     */
+    @NonNull
+    public static Intent createCancelUiIntent(@NonNull IBinder requestToken) {
+        Intent intent = new Intent();
+        ComponentName componentName =
+                ComponentName.unflattenFromString(
+                        Resources.getSystem()
+                                .getString(
+                                        com.android.internal.R.string
+                                                .config_credentialManagerDialogComponent));
+        intent.setComponent(componentName);
+        intent.putExtra(CancelUiRequest.EXTRA_CANCEL_UI_REQUEST, new CancelUiRequest(requestToken));
+        return intent;
+    }
+
+    /**
      * Notify the UI that providers have been enabled/disabled.
      *
      * @hide
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 965b35f..73157e6 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -17,10 +17,10 @@
 package android.hardware;
 
 import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
 import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
+import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import android.companion.virtual.VirtualDeviceManager;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 7766113..423fa42 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3065,6 +3065,7 @@
      *
      * @hide
      */
+    @TestApi
     public int getDisplayIdAssignedToUser() {
         try {
             return mService.getDisplayIdAssignedToUser();
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index ec1badb..8b55494 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
@@ -83,6 +84,22 @@
     public static final String ACTION_STYLUS_HANDWRITING_SETTINGS =
             "android.view.inputmethod.action.STYLUS_HANDWRITING_SETTINGS";
 
+    /**
+     * Maximal length of a component name
+     * @hide
+     */
+    @TestApi
+    public static final int COMPONENT_NAME_MAX_LENGTH = 1000;
+
+    /**
+     * The maximum amount of IMEs that are loaded per package (in order).
+     * If a package contains more IMEs, they will be ignored and cannot be enabled.
+     * @hide
+     */
+    @TestApi
+    @SuppressLint("MinMaxConstant")
+    public static final int MAX_IMES_PER_PACKAGE = 20;
+
     static final String TAG = "InputMethodInfo";
 
     /**
@@ -252,6 +269,13 @@
                     com.android.internal.R.styleable.InputMethod);
             settingsActivityComponent = sa.getString(
                     com.android.internal.R.styleable.InputMethod_settingsActivity);
+            if ((si.name != null && si.name.length() > COMPONENT_NAME_MAX_LENGTH) || (
+                    settingsActivityComponent != null
+                            && settingsActivityComponent.length() > COMPONENT_NAME_MAX_LENGTH)) {
+                throw new XmlPullParserException(
+                        "Activity name exceeds maximum of 1000 characters");
+            }
+
             isVrOnly = sa.getBoolean(com.android.internal.R.styleable.InputMethod_isVrOnly, false);
             isDefaultResId = sa.getResourceId(
                     com.android.internal.R.styleable.InputMethod_isDefault, 0);
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2f94ed7..209c785 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1304,6 +1304,13 @@
     <!-- Default LED off time for notification LED in milliseconds. -->
     <integer name="config_defaultNotificationLedOff">2000</integer>
 
+    <!-- LED behavior when battery is low.
+         Color for solid is taken from config_notificationsBatteryLowARGB
+          0 - default, solid when charging, flashing when not charging
+          1 - always solid when battery is low
+          2 - always flashing when battery is low -->
+    <integer name="config_notificationsBatteryLowBehavior">0</integer>
+
     <!-- Default value for led color when battery is low on charge -->
     <integer name="config_notificationsBatteryLowARGB">0xFFFF0000</integer>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 12646a0..8d56e7a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2004,6 +2004,7 @@
   <java-symbol type="integer" name="config_notificationsBatteryFullARGB" />
   <java-symbol type="integer" name="config_notificationsBatteryLedOff" />
   <java-symbol type="integer" name="config_notificationsBatteryLedOn" />
+  <java-symbol type="integer" name="config_notificationsBatteryLowBehavior" />
   <java-symbol type="integer" name="config_notificationsBatteryLowARGB" />
   <java-symbol type="integer" name="config_notificationsBatteryMediumARGB" />
   <java-symbol type="integer" name="config_notificationsBatteryNearlyFullLevel" />
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index e164e08..4f91e7a 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -16,7 +16,7 @@
 
 package android.app.activity;
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_INVALID;
+import static android.content.Context.DEVICE_ID_INVALID;
 import static android.content.Intent.ACTION_EDIT;
 import static android.content.Intent.ACTION_VIEW;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
diff --git a/core/tests/coretests/src/android/content/ContextTest.java b/core/tests/coretests/src/android/content/ContextTest.java
index 324f810..d478437 100644
--- a/core/tests/coretests/src/android/content/ContextTest.java
+++ b/core/tests/coretests/src/android/content/ContextTest.java
@@ -16,7 +16,7 @@
 
 package android.content;
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
+import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
 import static android.view.Display.DEFAULT_DISPLAY;
diff --git a/core/tests/mockingcoretests/Android.bp b/core/tests/mockingcoretests/Android.bp
index 96811be..29d7902 100644
--- a/core/tests/mockingcoretests/Android.bp
+++ b/core/tests/mockingcoretests/Android.bp
@@ -40,7 +40,6 @@
         "platform-test-annotations",
         "truth-prebuilt",
         "testables",
-        "ub-uiautomator",
     ],
 
     libs: [
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java
index 6a1313e..66fabec 100644
--- a/graphics/java/android/graphics/Mesh.java
+++ b/graphics/java/android/graphics/Mesh.java
@@ -341,7 +341,6 @@
      * @hide so only calls from module can utilize it
      */
     long getNativeWrapperInstance() {
-        nativeUpdateMesh(mNativeMeshWrapper, mIsIndexed);
         return mNativeMeshWrapper;
     }
 
@@ -383,5 +382,4 @@
 
     private static native void nativeUpdateUniforms(long builder, String uniformName, int[] values);
 
-    private static native void nativeUpdateMesh(long nativeMeshWrapper, boolean mIsIndexed);
 }
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index bcbe706..536bb49 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -332,6 +332,7 @@
         "jni/android_graphics_Matrix.cpp",
         "jni/android_graphics_Picture.cpp",
         "jni/android_graphics_DisplayListCanvas.cpp",
+        "jni/android_graphics_Mesh.cpp",
         "jni/android_graphics_RenderNode.cpp",
         "jni/android_nio_utils.cpp",
         "jni/android_util_PathParser.cpp",
@@ -351,7 +352,6 @@
         "jni/ImageDecoder.cpp",
         "jni/Interpolator.cpp",
         "jni/MeshSpecification.cpp",
-        "jni/Mesh.cpp",
         "jni/MaskFilter.cpp",
         "jni/NinePatch.cpp",
         "jni/NinePatchPeeker.cpp",
@@ -538,6 +538,7 @@
         "Interpolator.cpp",
         "LightingInfo.cpp",
         "Matrix.cpp",
+        "Mesh.cpp",
         "MemoryPolicy.cpp",
         "PathParser.cpp",
         "Properties.cpp",
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index e2127ef..a18ba1c 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -52,4 +52,5 @@
 X(DrawVectorDrawable)
 X(DrawRippleDrawable)
 X(DrawWebView)
-X(DrawMesh)
+X(DrawSkMesh)
+X(DrawMesh)
\ No newline at end of file
diff --git a/libs/hwui/Mesh.cpp b/libs/hwui/Mesh.cpp
new file mode 100644
index 0000000..e59bc95
--- /dev/null
+++ b/libs/hwui/Mesh.cpp
@@ -0,0 +1,102 @@
+/*
+ * 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 "Mesh.h"
+
+#include <GLES/gl.h>
+#include <SkMesh.h>
+
+#include "SafeMath.h"
+
+static size_t min_vcount_for_mode(SkMesh::Mode mode) {
+    switch (mode) {
+        case SkMesh::Mode::kTriangles:
+            return 3;
+        case SkMesh::Mode::kTriangleStrip:
+            return 3;
+    }
+}
+
+// Re-implementation of SkMesh::validate to validate user side that their mesh is valid.
+std::tuple<bool, SkString> Mesh::validate() {
+#define FAIL_MESH_VALIDATE(...) return std::make_tuple(false, SkStringPrintf(__VA_ARGS__))
+    if (!mMeshSpec) {
+        FAIL_MESH_VALIDATE("MeshSpecification is required.");
+    }
+    if (mVertexBufferData.empty()) {
+        FAIL_MESH_VALIDATE("VertexBuffer is required.");
+    }
+
+    auto meshStride = mMeshSpec->stride();
+    auto meshMode = SkMesh::Mode(mMode);
+    SafeMath sm;
+    size_t vsize = sm.mul(meshStride, mVertexCount);
+    if (sm.add(vsize, mVertexOffset) > mVertexBufferData.size()) {
+        FAIL_MESH_VALIDATE(
+                "The vertex buffer offset and vertex count reads beyond the end of the"
+                " vertex buffer.");
+    }
+
+    if (mVertexOffset % meshStride != 0) {
+        FAIL_MESH_VALIDATE("The vertex offset (%zu) must be a multiple of the vertex stride (%zu).",
+                           mVertexOffset, meshStride);
+    }
+
+    if (size_t uniformSize = mMeshSpec->uniformSize()) {
+        if (!mBuilder->fUniforms || mBuilder->fUniforms->size() < uniformSize) {
+            FAIL_MESH_VALIDATE("The uniform data is %zu bytes but must be at least %zu.",
+                               mBuilder->fUniforms->size(), uniformSize);
+        }
+    }
+
+    auto modeToStr = [](SkMesh::Mode m) {
+        switch (m) {
+            case SkMesh::Mode::kTriangles:
+                return "triangles";
+            case SkMesh::Mode::kTriangleStrip:
+                return "triangle-strip";
+        }
+    };
+    if (!mIndexBufferData.empty()) {
+        if (mIndexCount < min_vcount_for_mode(meshMode)) {
+            FAIL_MESH_VALIDATE("%s mode requires at least %zu indices but index count is %zu.",
+                               modeToStr(meshMode), min_vcount_for_mode(meshMode), mIndexCount);
+        }
+        size_t isize = sm.mul(sizeof(uint16_t), mIndexCount);
+        if (sm.add(isize, mIndexOffset) > mIndexBufferData.size()) {
+            FAIL_MESH_VALIDATE(
+                    "The index buffer offset and index count reads beyond the end of the"
+                    " index buffer.");
+        }
+        // If we allow 32 bit indices then this should enforce 4 byte alignment in that case.
+        if (!SkIsAlign2(mIndexOffset)) {
+            FAIL_MESH_VALIDATE("The index offset must be a multiple of 2.");
+        }
+    } else {
+        if (mVertexCount < min_vcount_for_mode(meshMode)) {
+            FAIL_MESH_VALIDATE("%s mode requires at least %zu vertices but vertex count is %zu.",
+                               modeToStr(meshMode), min_vcount_for_mode(meshMode), mVertexCount);
+        }
+        SkASSERT(!fICount);
+        SkASSERT(!fIOffset);
+    }
+
+    if (!sm.ok()) {
+        FAIL_MESH_VALIDATE("Overflow");
+    }
+#undef FAIL_MESH_VALIDATE
+    return {true, {}};
+}
diff --git a/libs/hwui/Mesh.h b/libs/hwui/Mesh.h
new file mode 100644
index 0000000..9836817
--- /dev/null
+++ b/libs/hwui/Mesh.h
@@ -0,0 +1,208 @@
+/*
+ * 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.
+ */
+
+#ifndef MESH_H_
+#define MESH_H_
+
+#include <GrDirectContext.h>
+#include <SkMesh.h>
+#include <jni.h>
+#include <log/log.h>
+
+#include <utility>
+
+class MeshUniformBuilder {
+public:
+    struct MeshUniform {
+        template <typename T>
+        std::enable_if_t<std::is_trivially_copyable<T>::value, MeshUniform> operator=(
+                const T& val) {
+            if (!fVar) {
+                LOG_FATAL("Assigning to missing variable");
+            } else if (sizeof(val) != fVar->sizeInBytes()) {
+                LOG_FATAL("Incorrect value size");
+            } else {
+                void* dst = reinterpret_cast<void*>(
+                        reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
+                memcpy(dst, &val, sizeof(val));
+            }
+        }
+
+        MeshUniform& operator=(const SkMatrix& val) {
+            if (!fVar) {
+                LOG_FATAL("Assigning to missing variable");
+            } else if (fVar->sizeInBytes() != 9 * sizeof(float)) {
+                LOG_FATAL("Incorrect value size");
+            } else {
+                float* data = reinterpret_cast<float*>(
+                        reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
+                data[0] = val.get(0);
+                data[1] = val.get(3);
+                data[2] = val.get(6);
+                data[3] = val.get(1);
+                data[4] = val.get(4);
+                data[5] = val.get(7);
+                data[6] = val.get(2);
+                data[7] = val.get(5);
+                data[8] = val.get(8);
+            }
+            return *this;
+        }
+
+        template <typename T>
+        bool set(const T val[], const int count) {
+            static_assert(std::is_trivially_copyable<T>::value, "Value must be trivial copyable");
+            if (!fVar) {
+                LOG_FATAL("Assigning to missing variable");
+                return false;
+            } else if (sizeof(T) * count != fVar->sizeInBytes()) {
+                LOG_FATAL("Incorrect value size");
+                return false;
+            } else {
+                void* dst = reinterpret_cast<void*>(
+                        reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
+                memcpy(dst, val, sizeof(T) * count);
+            }
+            return true;
+        }
+
+        MeshUniformBuilder* fOwner;
+        const SkRuntimeEffect::Uniform* fVar;
+    };
+    MeshUniform uniform(std::string_view name) { return {this, fMeshSpec->findUniform(name)}; }
+
+    explicit MeshUniformBuilder(sk_sp<SkMeshSpecification> meshSpec) {
+        fMeshSpec = sk_sp(meshSpec);
+        fUniforms = (SkData::MakeZeroInitialized(meshSpec->uniformSize()));
+    }
+
+    sk_sp<SkData> fUniforms;
+
+private:
+    void* writableUniformData() {
+        if (!fUniforms->unique()) {
+            fUniforms = SkData::MakeWithCopy(fUniforms->data(), fUniforms->size());
+        }
+        return fUniforms->writable_data();
+    }
+
+    sk_sp<SkMeshSpecification> fMeshSpec;
+};
+
+class Mesh {
+public:
+    Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode, const void* vertexBuffer,
+         size_t vertexBufferSize, jint vertexCount, jint vertexOffset,
+         std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
+            : mMeshSpec(meshSpec)
+            , mMode(mode)
+            , mVertexCount(vertexCount)
+            , mVertexOffset(vertexOffset)
+            , mBuilder(std::move(builder))
+            , mBounds(bounds) {
+        copyToVector(mVertexBufferData, vertexBuffer, vertexBufferSize);
+    }
+
+    Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode, const void* vertexBuffer,
+         size_t vertexBufferSize, jint vertexCount, jint vertexOffset, const void* indexBuffer,
+         size_t indexBufferSize, jint indexCount, jint indexOffset,
+         std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
+            : mMeshSpec(meshSpec)
+            , mMode(mode)
+            , mVertexCount(vertexCount)
+            , mVertexOffset(vertexOffset)
+            , mIndexCount(indexCount)
+            , mIndexOffset(indexOffset)
+            , mBuilder(std::move(builder))
+            , mBounds(bounds) {
+        copyToVector(mVertexBufferData, vertexBuffer, vertexBufferSize);
+        copyToVector(mIndexBufferData, indexBuffer, indexBufferSize);
+    }
+
+    Mesh(Mesh&&) = default;
+
+    Mesh& operator=(Mesh&&) = default;
+
+    [[nodiscard]] std::tuple<bool, SkString> validate();
+
+    void updateSkMesh(GrDirectContext* context) const {
+        GrDirectContext::DirectContextID genId = GrDirectContext::DirectContextID();
+        if (context) {
+            genId = context->directContextID();
+        }
+
+        if (mIsDirty || genId != mGenerationId) {
+            auto vb = SkMesh::MakeVertexBuffer(
+                    context, reinterpret_cast<const void*>(mVertexBufferData.data()),
+                    mVertexBufferData.size());
+            auto meshMode = SkMesh::Mode(mMode);
+            if (!mIndexBufferData.empty()) {
+                auto ib = SkMesh::MakeIndexBuffer(
+                        context, reinterpret_cast<const void*>(mIndexBufferData.data()),
+                        mIndexBufferData.size());
+                mMesh = SkMesh::MakeIndexed(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
+                                            ib, mIndexCount, mIndexOffset, mBuilder->fUniforms,
+                                            mBounds)
+                                .mesh;
+            } else {
+                mMesh = SkMesh::Make(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
+                                     mBuilder->fUniforms, mBounds)
+                                .mesh;
+            }
+            mIsDirty = false;
+            mGenerationId = genId;
+        }
+    }
+
+    SkMesh& getSkMesh() const {
+        LOG_FATAL_IF(mIsDirty,
+                     "Attempt to obtain SkMesh when Mesh is dirty, did you "
+                     "forget to call updateSkMesh with a GrDirectContext? "
+                     "Defensively creating a CPU mesh");
+        return mMesh;
+    }
+
+    void markDirty() { mIsDirty = true; }
+
+    MeshUniformBuilder* uniformBuilder() { return mBuilder.get(); }
+
+private:
+    void copyToVector(std::vector<uint8_t>& dst, const void* src, size_t srcSize) {
+        if (src) {
+            dst.resize(srcSize);
+            memcpy(dst.data(), src, srcSize);
+        }
+    }
+
+    sk_sp<SkMeshSpecification> mMeshSpec;
+    int mMode = 0;
+
+    std::vector<uint8_t> mVertexBufferData;
+    size_t mVertexCount = 0;
+    size_t mVertexOffset = 0;
+
+    std::vector<uint8_t> mIndexBufferData;
+    size_t mIndexCount = 0;
+    size_t mIndexOffset = 0;
+
+    std::unique_ptr<MeshUniformBuilder> mBuilder;
+    SkRect mBounds{};
+
+    mutable SkMesh mMesh{};
+    mutable bool mIsDirty = true;
+    mutable GrDirectContext::DirectContextID mGenerationId = GrDirectContext::DirectContextID();
+};
+#endif  // MESH_H_
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 659aec0..0b58406 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -24,6 +24,7 @@
 #include <experimental/type_traits>
 #include <utility>
 
+#include "Mesh.h"
 #include "SkAndroidFrameworkUtils.h"
 #include "SkBlendMode.h"
 #include "SkCanvas.h"
@@ -502,14 +503,14 @@
         c->drawVertices(vertices, mode, paint);
     }
 };
-struct DrawMesh final : Op {
-    static const auto kType = Type::DrawMesh;
-    DrawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
+struct DrawSkMesh final : Op {
+    static const auto kType = Type::DrawSkMesh;
+    DrawSkMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
             : cpuMesh(mesh), blender(std::move(blender)), paint(paint) {
         isGpuBased = false;
     }
 
-    SkMesh cpuMesh;
+    const SkMesh& cpuMesh;
     mutable SkMesh gpuMesh;
     sk_sp<SkBlender> blender;
     SkPaint paint;
@@ -517,6 +518,7 @@
     mutable GrDirectContext::DirectContextID contextId;
     void draw(SkCanvas* c, const SkMatrix&) const {
         GrDirectContext* directContext = c->recordingContext()->asDirectContext();
+
         GrDirectContext::DirectContextID id = directContext->directContextID();
         if (!isGpuBased || contextId != id) {
             sk_sp<SkMesh::VertexBuffer> vb =
@@ -543,6 +545,18 @@
         c->drawMesh(gpuMesh, blender, paint);
     }
 };
+
+struct DrawMesh final : Op {
+    static const auto kType = Type::DrawMesh;
+    DrawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
+            : mesh(mesh), blender(std::move(blender)), paint(paint) {}
+
+    const Mesh& mesh;
+    sk_sp<SkBlender> blender;
+    SkPaint paint;
+
+    void draw(SkCanvas* c, const SkMatrix&) const { c->drawMesh(mesh.getSkMesh(), blender, paint); }
+};
 struct DrawAtlas final : Op {
     static const auto kType = Type::DrawAtlas;
     DrawAtlas(const SkImage* atlas, int count, SkBlendMode mode, const SkSamplingOptions& sampling,
@@ -859,6 +873,10 @@
 }
 void DisplayListData::drawMesh(const SkMesh& mesh, const sk_sp<SkBlender>& blender,
                                const SkPaint& paint) {
+    this->push<DrawSkMesh>(0, mesh, blender, paint);
+}
+void DisplayListData::drawMesh(const Mesh& mesh, const sk_sp<SkBlender>& blender,
+                               const SkPaint& paint) {
     this->push<DrawMesh>(0, mesh, blender, paint);
 }
 void DisplayListData::drawAtlas(const SkImage* atlas, const SkRSXform xforms[], const SkRect texs[],
@@ -1205,6 +1223,9 @@
                                  const SkPaint& paint) {
     fDL->drawMesh(mesh, blender, paint);
 }
+void RecordingCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint) {
+    fDL->drawMesh(mesh, blender, paint);
+}
 void RecordingCanvas::onDrawAtlas2(const SkImage* atlas, const SkRSXform xforms[],
                                    const SkRect texs[], const SkColor colors[], int count,
                                    SkBlendMode bmode, const SkSamplingOptions& sampling,
@@ -1223,5 +1244,14 @@
     fDL->drawWebView(drawable);
 }
 
+[[nodiscard]] const SkMesh& DrawMeshPayload::getSkMesh() const {
+    LOG_FATAL_IF(!meshWrapper && !mesh, "One of Mesh or Mesh must be non-null");
+    if (meshWrapper) {
+        return meshWrapper->getSkMesh();
+    } else {
+        return *mesh;
+    }
+}
+
 }  // namespace uirenderer
 }  // namespace android
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 8409e13..1f4ba5d 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -28,6 +28,7 @@
 #include <log/log.h>
 
 #include <cstdlib>
+#include <utility>
 #include <vector>
 
 #include "CanvasTransform.h"
@@ -40,6 +41,7 @@
 
 enum class SkBlendMode;
 class SkRRect;
+class Mesh;
 
 namespace android {
 namespace uirenderer {
@@ -66,6 +68,18 @@
 
 static_assert(sizeof(DisplayListOp) == 4);
 
+class DrawMeshPayload {
+public:
+    explicit DrawMeshPayload(const SkMesh* mesh) : mesh(mesh) {}
+    explicit DrawMeshPayload(const Mesh* meshWrapper) : meshWrapper(meshWrapper) {}
+
+    [[nodiscard]] const SkMesh& getSkMesh() const;
+
+private:
+    const SkMesh* mesh = nullptr;
+    const Mesh* meshWrapper = nullptr;
+};
+
 struct DrawImagePayload {
     explicit DrawImagePayload(Bitmap& bitmap)
             : image(bitmap.makeImage()), palette(bitmap.palette()) {
@@ -143,6 +157,7 @@
     void drawDRRect(const SkRRect&, const SkRRect&, const SkPaint&);
 
     void drawMesh(const SkMesh&, const sk_sp<SkBlender>&, const SkPaint&);
+    void drawMesh(const Mesh&, const sk_sp<SkBlender>&, const SkPaint&);
 
     void drawAnnotation(const SkRect&, const char*, SkData*);
     void drawDrawable(SkDrawable*, const SkMatrix*);
@@ -247,6 +262,7 @@
                      SkBlendMode, const SkSamplingOptions&, const SkRect*, const SkPaint*) override;
     void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override;
 
+    void drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint);
     void drawVectorDrawable(VectorDrawableRoot* tree);
     void drawWebView(skiapipeline::FunctorDrawable*);
 
diff --git a/libs/hwui/SafeMath.h b/libs/hwui/SafeMath.h
new file mode 100644
index 0000000..4d6adf5
--- /dev/null
+++ b/libs/hwui/SafeMath.h
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ */
+
+#ifndef SkSafeMath_DEFINED
+#define SkSafeMath_DEFINED
+
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+
+// Copy of Skia's SafeMath API used to validate Mesh parameters to support
+// deferred creation of SkMesh instances on RenderThread.
+// SafeMath always check that a series of operations do not overflow.
+// This must be correct for all platforms, because this is a check for safety at runtime.
+
+class SafeMath {
+public:
+    SafeMath() = default;
+
+    bool ok() const { return fOK; }
+    explicit operator bool() const { return fOK; }
+
+    size_t mul(size_t x, size_t y) {
+        return sizeof(size_t) == sizeof(uint64_t) ? mul64(x, y) : mul32(x, y);
+    }
+
+    size_t add(size_t x, size_t y) {
+        size_t result = x + y;
+        fOK &= result >= x;
+        return result;
+    }
+
+    /**
+     *  Return a + b, unless this result is an overflow/underflow. In those cases, fOK will
+     *  be set to false, and it is undefined what this returns.
+     */
+    int addInt(int a, int b) {
+        if (b < 0 && a < std::numeric_limits<int>::min() - b) {
+            fOK = false;
+            return a;
+        } else if (b > 0 && a > std::numeric_limits<int>::max() - b) {
+            fOK = false;
+            return a;
+        }
+        return a + b;
+    }
+
+    // These saturate to their results
+    static size_t Add(size_t x, size_t y) {
+        SafeMath tmp;
+        size_t sum = tmp.add(x, y);
+        return tmp.ok() ? sum : SIZE_MAX;
+    }
+
+    static size_t Mul(size_t x, size_t y) {
+        SafeMath tmp;
+        size_t prod = tmp.mul(x, y);
+        return tmp.ok() ? prod : SIZE_MAX;
+    }
+
+private:
+    uint32_t mul32(uint32_t x, uint32_t y) {
+        uint64_t bx = x;
+        uint64_t by = y;
+        uint64_t result = bx * by;
+        fOK &= result >> 32 == 0;
+        // Overflow information is capture in fOK. Return the result modulo 2^32.
+        return (uint32_t)result;
+    }
+
+    uint64_t mul64(uint64_t x, uint64_t y) {
+        if (x <= std::numeric_limits<uint64_t>::max() >> 32 &&
+            y <= std::numeric_limits<uint64_t>::max() >> 32) {
+            return x * y;
+        } else {
+            auto hi = [](uint64_t x) { return x >> 32; };
+            auto lo = [](uint64_t x) { return x & 0xFFFFFFFF; };
+
+            uint64_t lx_ly = lo(x) * lo(y);
+            uint64_t hx_ly = hi(x) * lo(y);
+            uint64_t lx_hy = lo(x) * hi(y);
+            uint64_t hx_hy = hi(x) * hi(y);
+            uint64_t result = 0;
+            result = this->add(lx_ly, (hx_ly << 32));
+            result = this->add(result, (lx_hy << 32));
+            fOK &= (hx_hy + (hx_ly >> 32) + (lx_hy >> 32)) == 0;
+
+            return result;
+        }
+    }
+    bool fOK = true;
+};
+
+#endif  // SkSafeMath_DEFINED
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index d0124f5..7a12769 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -39,21 +39,22 @@
 #include <SkShader.h>
 #include <SkTextBlob.h>
 #include <SkVertices.h>
+#include <log/log.h>
+#include <ui/FatVector.h>
 
 #include <memory>
 #include <optional>
 #include <utility>
 
 #include "CanvasProperty.h"
+#include "Mesh.h"
 #include "NinePatchUtils.h"
 #include "VectorDrawable.h"
 #include "hwui/Bitmap.h"
 #include "hwui/MinikinUtils.h"
 #include "hwui/PaintFilter.h"
-#include <log/log.h>
 #include "pipeline/skia/AnimatedDrawables.h"
 #include "pipeline/skia/HolePunch.h"
-#include <ui/FatVector.h>
 
 namespace android {
 
@@ -572,8 +573,14 @@
     applyLooper(&paint, [&](const SkPaint& p) { mCanvas->drawVertices(vertices, mode, p); });
 }
 
-void SkiaCanvas::drawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint) {
-    mCanvas->drawMesh(mesh, blender, paint);
+void SkiaCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) {
+    GrDirectContext* context = nullptr;
+    auto recordingContext = mCanvas->recordingContext();
+    if (recordingContext) {
+        context = recordingContext->asDirectContext();
+    }
+    mesh.updateSkMesh(context);
+    mCanvas->drawMesh(mesh.getSkMesh(), blender, paint);
 }
 
 // ----------------------------------------------------------------------------
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index f2c286a..b785989 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -129,8 +129,7 @@
                          float sweepAngle, bool useCenter, const Paint& paint) override;
     virtual void drawPath(const SkPath& path, const Paint& paint) override;
     virtual void drawVertices(const SkVertices*, SkBlendMode, const Paint& paint) override;
-    virtual void drawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender,
-                          const SkPaint& paint) override;
+    virtual void drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) override;
 
     virtual void drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) override;
     virtual void drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const Paint* paint) override;
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 2a20191..44ee31d 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -16,18 +16,17 @@
 
 #pragma once
 
-#include <cutils/compiler.h>
-#include <utils/Functor.h>
 #include <SaveFlags.h>
-
-#include <androidfw/ResourceTypes.h>
-#include "Properties.h"
-#include "pipeline/skia/AnimatedDrawables.h"
-#include "utils/Macros.h"
-
 #include <SkBitmap.h>
 #include <SkCanvas.h>
 #include <SkMatrix.h>
+#include <androidfw/ResourceTypes.h>
+#include <cutils/compiler.h>
+#include <utils/Functor.h>
+
+#include "Properties.h"
+#include "pipeline/skia/AnimatedDrawables.h"
+#include "utils/Macros.h"
 
 class SkAnimatedImage;
 enum class SkBlendMode;
@@ -35,6 +34,7 @@
 class SkRRect;
 class SkRuntimeShaderBuilder;
 class SkVertices;
+class Mesh;
 
 namespace minikin {
 class Font;
@@ -227,7 +227,7 @@
                          float sweepAngle, bool useCenter, const Paint& paint) = 0;
     virtual void drawPath(const SkPath& path, const Paint& paint) = 0;
     virtual void drawVertices(const SkVertices*, SkBlendMode, const Paint& paint) = 0;
-    virtual void drawMesh(const SkMesh& mesh, sk_sp<SkBlender>, const SkPaint& paint) = 0;
+    virtual void drawMesh(const Mesh& mesh, sk_sp<SkBlender>, const Paint& paint) = 0;
 
     // Bitmap-based
     virtual void drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) = 0;
diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h
index 6b983c1..24f9e82 100644
--- a/libs/hwui/jni/GraphicsJNI.h
+++ b/libs/hwui/jni/GraphicsJNI.h
@@ -46,10 +46,16 @@
 
     static void setJavaVM(JavaVM* javaVM);
 
-    /** returns a pointer to the JavaVM provided when we initialized the module */
+    /**
+     * returns a pointer to the JavaVM provided when we initialized the module
+     * DEPRECATED: Objects should know the JavaVM that created them
+     */
     static JavaVM* getJavaVM() { return mJavaVM; }
 
-    /** return a pointer to the JNIEnv for this thread */
+    /**
+     * return a pointer to the JNIEnv for this thread
+     * DEPRECATED: Objects should know the JavaVM that created them
+     */
     static JNIEnv* getJNIEnv();
 
     /** create a JNIEnv* for this thread or assert if one already exists */
@@ -337,13 +343,21 @@
     JGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm), mObject(object) {}
 
     virtual ~JGlobalRefHolder() {
-        GraphicsJNI::getJNIEnv()->DeleteGlobalRef(mObject);
+        env()->DeleteGlobalRef(mObject);
         mObject = nullptr;
     }
 
     jobject object() { return mObject; }
     JavaVM* vm() { return mVm; }
 
+    JNIEnv* env() {
+        JNIEnv* env;
+        if (mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+            LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", mVm);
+        }
+        return env;
+    }
+
 private:
     JGlobalRefHolder(const JGlobalRefHolder&) = delete;
     void operator=(const JGlobalRefHolder&) = delete;
diff --git a/libs/hwui/jni/JvmErrorReporter.h b/libs/hwui/jni/JvmErrorReporter.h
index 5e10b9d..3a35875 100644
--- a/libs/hwui/jni/JvmErrorReporter.h
+++ b/libs/hwui/jni/JvmErrorReporter.h
@@ -30,7 +30,10 @@
     JvmErrorReporter(JNIEnv* env) { env->GetJavaVM(&mVm); }
 
     virtual void onError(const std::string& message) override {
-        JNIEnv* env = GraphicsJNI::getJNIEnv();
+        JNIEnv* env;
+        if (mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+            LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", mVm);
+        }
         jniThrowException(env, "java/lang/IllegalStateException", message.c_str());
     }
 
diff --git a/libs/hwui/jni/Mesh.cpp b/libs/hwui/jni/Mesh.cpp
deleted file mode 100644
index b13d9ba..0000000
--- a/libs/hwui/jni/Mesh.cpp
+++ /dev/null
@@ -1,227 +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 <GLES/gl.h>
-#include <Mesh.h>
-#include <SkMesh.h>
-
-#include "GraphicsJNI.h"
-#include "graphics_jni_helpers.h"
-
-namespace android {
-
-sk_sp<SkMesh::VertexBuffer> genVertexBuffer(JNIEnv* env, jobject buffer, int size,
-                                            jboolean isDirect) {
-    auto buff = ScopedJavaNioBuffer(env, buffer, size, isDirect);
-    auto vertexBuffer = SkMesh::MakeVertexBuffer(nullptr, buff.data(), size);
-    return vertexBuffer;
-}
-
-sk_sp<SkMesh::IndexBuffer> genIndexBuffer(JNIEnv* env, jobject buffer, int size,
-                                          jboolean isDirect) {
-    auto buff = ScopedJavaNioBuffer(env, buffer, size, isDirect);
-    auto indexBuffer = SkMesh::MakeIndexBuffer(nullptr, buff.data(), size);
-    return indexBuffer;
-}
-
-static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
-                  jboolean isDirect, jint vertexCount, jint vertexOffset, jfloat left, jfloat top,
-                  jfloat right, jfloat bottom) {
-    auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
-    sk_sp<SkMesh::VertexBuffer> skVertexBuffer =
-            genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isDirect);
-    auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
-    auto meshResult = SkMesh::Make(
-            skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset,
-            SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect);
-
-    if (!meshResult.error.isEmpty()) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str());
-    }
-
-    auto meshPtr = std::make_unique<MeshWrapper>(
-            MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)});
-    return reinterpret_cast<jlong>(meshPtr.release());
-}
-
-static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
-                         jboolean isVertexDirect, jint vertexCount, jint vertexOffset,
-                         jobject indexBuffer, jboolean isIndexDirect, jint indexCount,
-                         jint indexOffset, jfloat left, jfloat top, jfloat right, jfloat bottom) {
-    auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
-    sk_sp<SkMesh::VertexBuffer> skVertexBuffer =
-            genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isVertexDirect);
-    sk_sp<SkMesh::IndexBuffer> skIndexBuffer =
-            genIndexBuffer(env, indexBuffer, indexCount * gIndexByteSize, isIndexDirect);
-    auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
-
-    auto meshResult = SkMesh::MakeIndexed(
-            skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset,
-            skIndexBuffer, indexCount, indexOffset,
-            SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect);
-
-    if (!meshResult.error.isEmpty()) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str());
-    }
-    auto meshPtr = std::make_unique<MeshWrapper>(
-            MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)});
-    return reinterpret_cast<jlong>(meshPtr.release());
-}
-
-static void updateMesh(JNIEnv* env, jobject, jlong meshWrapper, jboolean indexed) {
-    auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
-    auto mesh = wrapper->mesh;
-    if (indexed) {
-        wrapper->mesh = SkMesh::MakeIndexed(sk_ref_sp(mesh.spec()), mesh.mode(),
-                                            sk_ref_sp(mesh.vertexBuffer()), mesh.vertexCount(),
-                                            mesh.vertexOffset(), sk_ref_sp(mesh.indexBuffer()),
-                                            mesh.indexCount(), mesh.indexOffset(),
-                                            wrapper->builder.fUniforms, mesh.bounds())
-                                .mesh;
-    } else {
-        wrapper->mesh = SkMesh::Make(sk_ref_sp(mesh.spec()), mesh.mode(),
-                                     sk_ref_sp(mesh.vertexBuffer()), mesh.vertexCount(),
-                                     mesh.vertexOffset(), wrapper->builder.fUniforms, mesh.bounds())
-                                .mesh;
-    }
-}
-
-static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) {
-    va_list args;
-    va_start(args, fmt);
-    int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args);
-    va_end(args);
-    return ret;
-}
-
-static bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) {
-    switch (type) {
-        case SkRuntimeEffect::Uniform::Type::kFloat:
-        case SkRuntimeEffect::Uniform::Type::kFloat2:
-        case SkRuntimeEffect::Uniform::Type::kFloat3:
-        case SkRuntimeEffect::Uniform::Type::kFloat4:
-        case SkRuntimeEffect::Uniform::Type::kFloat2x2:
-        case SkRuntimeEffect::Uniform::Type::kFloat3x3:
-        case SkRuntimeEffect::Uniform::Type::kFloat4x4:
-            return false;
-        case SkRuntimeEffect::Uniform::Type::kInt:
-        case SkRuntimeEffect::Uniform::Type::kInt2:
-        case SkRuntimeEffect::Uniform::Type::kInt3:
-        case SkRuntimeEffect::Uniform::Type::kInt4:
-            return true;
-    }
-}
-
-static void nativeUpdateFloatUniforms(JNIEnv* env, MeshUniformBuilder* builder,
-                                      const char* uniformName, const float values[], int count,
-                                      bool isColor) {
-    MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName);
-    if (uniform.fVar == nullptr) {
-        ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
-    } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) {
-        if (isColor) {
-            jniThrowExceptionFmt(
-                    env, "java/lang/IllegalArgumentException",
-                    "attempting to set a color uniform using the non-color specific APIs: %s %x",
-                    uniformName, uniform.fVar->flags);
-        } else {
-            ThrowIAEFmt(env,
-                        "attempting to set a non-color uniform using the setColorUniform APIs: %s",
-                        uniformName);
-        }
-    } else if (isIntUniformType(uniform.fVar->type)) {
-        ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s",
-                    uniformName);
-    } else if (!uniform.set<float>(values, count)) {
-        ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
-                    uniform.fVar->sizeInBytes(), sizeof(float) * count);
-    }
-}
-
-static void updateFloatUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
-                                jfloat value1, jfloat value2, jfloat value3, jfloat value4,
-                                jint count) {
-    auto* wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
-    ScopedUtfChars name(env, uniformName);
-    const float values[4] = {value1, value2, value3, value4};
-    nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), values, count, false);
-}
-
-static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName,
-                                     jfloatArray jvalues, jboolean isColor) {
-    auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
-    ScopedUtfChars name(env, jUniformName);
-    AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
-    nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(),
-                              autoValues.length(), isColor);
-}
-
-static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
-                                    const char* uniformName, const int values[], int count) {
-    MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName);
-    if (uniform.fVar == nullptr) {
-        ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
-    } else if (!isIntUniformType(uniform.fVar->type)) {
-        ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s",
-                    uniformName);
-    } else if (!uniform.set<int>(values, count)) {
-        ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
-                    uniform.fVar->sizeInBytes(), sizeof(float) * count);
-    }
-}
-
-static void updateIntUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
-                              jint value1, jint value2, jint value3, jint value4, jint count) {
-    auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
-    ScopedUtfChars name(env, uniformName);
-    const int values[4] = {value1, value2, value3, value4};
-    nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), values, count);
-}
-
-static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
-                                   jintArray values) {
-    auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
-    ScopedUtfChars name(env, uniformName);
-    AutoJavaIntArray autoValues(env, values, 0);
-    nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(),
-                            autoValues.length());
-}
-
-static void MeshWrapper_destroy(MeshWrapper* wrapper) {
-    delete wrapper;
-}
-
-static jlong getMeshFinalizer(JNIEnv*, jobject) {
-    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshWrapper_destroy));
-}
-
-static const JNINativeMethod gMeshMethods[] = {
-        {"nativeGetFinalizer", "()J", (void*)getMeshFinalizer},
-        {"nativeMake", "(JILjava/nio/Buffer;ZIIFFFF)J", (void*)make},
-        {"nativeMakeIndexed", "(JILjava/nio/Buffer;ZIILjava/nio/ShortBuffer;ZIIFFFF)J",
-         (void*)makeIndexed},
-        {"nativeUpdateMesh", "(JZ)V", (void*)updateMesh},
-        {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V", (void*)updateFloatArrayUniforms},
-        {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V", (void*)updateFloatUniforms},
-        {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V", (void*)updateIntArrayUniforms},
-        {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V", (void*)updateIntUniforms}};
-
-int register_android_graphics_Mesh(JNIEnv* env) {
-    android::RegisterMethodsOrDie(env, "android/graphics/Mesh", gMeshMethods, NELEM(gMeshMethods));
-    return 0;
-}
-
-}  // namespace android
diff --git a/libs/hwui/jni/Mesh.h b/libs/hwui/jni/Mesh.h
deleted file mode 100644
index 61c2260..0000000
--- a/libs/hwui/jni/Mesh.h
+++ /dev/null
@@ -1,265 +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.
- */
-
-#ifndef FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_
-#define FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_
-
-#include <SkMesh.h>
-#include <jni.h>
-
-#include <log/log.h>
-#include <utility>
-
-#include "graphics_jni_helpers.h"
-
-#define gIndexByteSize 2
-
-// A smart pointer that provides read only access to Java.nio.Buffer. This handles both
-// direct and indrect buffers, allowing access to the underlying data in both
-// situations. If passed a null buffer, we will throw NullPointerException,
-// and c_data will return nullptr.
-//
-// This class draws from com_google_android_gles_jni_GLImpl.cpp for Buffer to void *
-// conversion.
-class ScopedJavaNioBuffer {
-public:
-    ScopedJavaNioBuffer(JNIEnv* env, jobject buffer, jint size, jboolean isDirect)
-            : mEnv(env), mBuffer(buffer) {
-        if (buffer == nullptr) {
-            mDataBase = nullptr;
-            mData = nullptr;
-            jniThrowNullPointerException(env);
-        } else {
-            mArray = (jarray) nullptr;
-            if (isDirect) {
-                mData = getDirectBufferPointer(mEnv, mBuffer);
-            } else {
-                mData = setIndirectData(size);
-            }
-        }
-    }
-
-    ScopedJavaNioBuffer(ScopedJavaNioBuffer&& rhs) noexcept { *this = std::move(rhs); }
-
-    ~ScopedJavaNioBuffer() { reset(); }
-
-    void reset() {
-        if (mDataBase) {
-            releasePointer(mEnv, mArray, mDataBase, JNI_FALSE);
-            mDataBase = nullptr;
-        }
-    }
-
-    ScopedJavaNioBuffer& operator=(ScopedJavaNioBuffer&& rhs) noexcept {
-        if (this != &rhs) {
-            reset();
-
-            mEnv = rhs.mEnv;
-            mBuffer = rhs.mBuffer;
-            mDataBase = rhs.mDataBase;
-            mData = rhs.mData;
-            mArray = rhs.mArray;
-            rhs.mEnv = nullptr;
-            rhs.mData = nullptr;
-            rhs.mBuffer = nullptr;
-            rhs.mArray = nullptr;
-            rhs.mDataBase = nullptr;
-        }
-        return *this;
-    }
-
-    const void* data() const { return mData; }
-
-private:
-    /**
-     * This code is taken and modified from com_google_android_gles_jni_GLImpl.cpp to extract data
-     * from a java.nio.Buffer.
-     */
-    void* getDirectBufferPointer(JNIEnv* env, jobject buffer) {
-        if (buffer == nullptr) {
-            return nullptr;
-        }
-
-        jint position;
-        jint limit;
-        jint elementSizeShift;
-        jlong pointer;
-        pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
-        if (pointer == 0) {
-            jniThrowException(mEnv, "java/lang/IllegalArgumentException",
-                              "Must use a native order direct Buffer");
-            return nullptr;
-        }
-        pointer += position << elementSizeShift;
-        return reinterpret_cast<void*>(pointer);
-    }
-
-    static void releasePointer(JNIEnv* env, jarray array, void* data, jboolean commit) {
-        env->ReleasePrimitiveArrayCritical(array, data, commit ? 0 : JNI_ABORT);
-    }
-
-    static void* getPointer(JNIEnv* env, jobject buffer, jarray* array, jint* remaining,
-                            jint* offset) {
-        jint position;
-        jint limit;
-        jint elementSizeShift;
-
-        jlong pointer;
-        pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
-        *remaining = (limit - position) << elementSizeShift;
-        if (pointer != 0L) {
-            *array = nullptr;
-            pointer += position << elementSizeShift;
-            return reinterpret_cast<void*>(pointer);
-        }
-
-        *array = jniGetNioBufferBaseArray(env, buffer);
-        *offset = jniGetNioBufferBaseArrayOffset(env, buffer);
-        return nullptr;
-    }
-
-    /**
-     * This is a copy of
-     * static void android_glBufferData__IILjava_nio_Buffer_2I
-     * from com_google_android_gles_jni_GLImpl.cpp
-     */
-    void* setIndirectData(jint size) {
-        jint exception;
-        const char* exceptionType;
-        const char* exceptionMessage;
-        jint bufferOffset = (jint)0;
-        jint remaining;
-        void* tempData;
-
-        if (mBuffer) {
-            tempData =
-                    (void*)getPointer(mEnv, mBuffer, (jarray*)&mArray, &remaining, &bufferOffset);
-            if (remaining < size) {
-                exception = 1;
-                exceptionType = "java/lang/IllegalArgumentException";
-                exceptionMessage = "remaining() < size < needed";
-                goto exit;
-            }
-        }
-        if (mBuffer && tempData == nullptr) {
-            mDataBase = (char*)mEnv->GetPrimitiveArrayCritical(mArray, (jboolean*)0);
-            tempData = (void*)(mDataBase + bufferOffset);
-        }
-        return tempData;
-    exit:
-        if (mArray) {
-            releasePointer(mEnv, mArray, (void*)(mDataBase), JNI_FALSE);
-        }
-        if (exception) {
-            jniThrowException(mEnv, exceptionType, exceptionMessage);
-        }
-        return nullptr;
-    }
-
-    JNIEnv* mEnv;
-
-    // Java Buffer data
-    void* mData;
-    jobject mBuffer;
-
-    // Indirect Buffer Data
-    jarray mArray;
-    char* mDataBase;
-};
-
-class MeshUniformBuilder {
-public:
-    struct MeshUniform {
-        template <typename T>
-        std::enable_if_t<std::is_trivially_copyable<T>::value, MeshUniform> operator=(
-                const T& val) {
-            if (!fVar) {
-                LOG_FATAL("Assigning to missing variable");
-            } else if (sizeof(val) != fVar->sizeInBytes()) {
-                LOG_FATAL("Incorrect value size");
-            } else {
-                void* dst = reinterpret_cast<void*>(
-                    reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
-                memcpy(dst, &val, sizeof(val));
-            }
-        }
-
-        MeshUniform& operator=(const SkMatrix& val) {
-            if (!fVar) {
-                LOG_FATAL("Assigning to missing variable");
-            } else if (fVar->sizeInBytes() != 9 * sizeof(float)) {
-                LOG_FATAL("Incorrect value size");
-            } else {
-                float* data = reinterpret_cast<float*>(
-                    reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
-                data[0] = val.get(0);
-                data[1] = val.get(3);
-                data[2] = val.get(6);
-                data[3] = val.get(1);
-                data[4] = val.get(4);
-                data[5] = val.get(7);
-                data[6] = val.get(2);
-                data[7] = val.get(5);
-                data[8] = val.get(8);
-            }
-            return *this;
-        }
-
-        template <typename T>
-        bool set(const T val[], const int count) {
-            static_assert(std::is_trivially_copyable<T>::value, "Value must be trivial copyable");
-            if (!fVar) {
-                LOG_FATAL("Assigning to missing variable");
-                return false;
-            } else if (sizeof(T) * count != fVar->sizeInBytes()) {
-                LOG_FATAL("Incorrect value size");
-                return false;
-            } else {
-                void* dst = reinterpret_cast<void*>(
-                    reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
-                memcpy(dst, val, sizeof(T) * count);
-            }
-            return true;
-        }
-
-        MeshUniformBuilder* fOwner;
-        const SkRuntimeEffect::Uniform* fVar;
-    };
-    MeshUniform uniform(std::string_view name) { return {this, fMeshSpec->findUniform(name)}; }
-
-    explicit MeshUniformBuilder(sk_sp<SkMeshSpecification> meshSpec) {
-        fMeshSpec = sk_sp(meshSpec);
-        fUniforms = (SkData::MakeZeroInitialized(meshSpec->uniformSize()));
-    }
-
-    sk_sp<SkData> fUniforms;
-
-private:
-    void* writableUniformData() {
-        if (!fUniforms->unique()) {
-            fUniforms = SkData::MakeWithCopy(fUniforms->data(), fUniforms->size());
-        }
-        return fUniforms->writable_data();
-    }
-
-    sk_sp<SkMeshSpecification> fMeshSpec;
-};
-
-struct MeshWrapper {
-    SkMesh mesh;
-    MeshUniformBuilder builder;
-};
-#endif  // FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 8a4d4e1..8ba7503 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -21,7 +21,6 @@
 #else
 #define __ANDROID_API_P__ 28
 #endif
-#include <Mesh.h>
 #include <androidfw/ResourceTypes.h>
 #include <hwui/Canvas.h>
 #include <hwui/Paint.h>
@@ -446,10 +445,10 @@
 
 static void drawMesh(JNIEnv* env, jobject, jlong canvasHandle, jlong meshHandle, jint modeHandle,
                      jlong paintHandle) {
-    const SkMesh mesh = reinterpret_cast<MeshWrapper*>(meshHandle)->mesh;
+    const Mesh* mesh = reinterpret_cast<Mesh*>(meshHandle);
     SkBlendMode blendMode = static_cast<SkBlendMode>(modeHandle);
-    SkPaint* paint = reinterpret_cast<Paint*>(paintHandle);
-    get_canvas(canvasHandle)->drawMesh(mesh, SkBlender::Mode(blendMode), *paint);
+    Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+    get_canvas(canvasHandle)->drawMesh(*mesh, SkBlender::Mode(blendMode), *paint);
 }
 
 static void drawNinePatch(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
index 3e453e6..ae22213 100644
--- a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
@@ -49,7 +49,7 @@
     auto globalCallbackRef =
             std::make_shared<JGlobalRefHolder>(vm, env->NewGlobalRef(releaseCallback));
     return [globalCallbackRef](android::base::unique_fd&& fd, int status) {
-        GraphicsJNI::getJNIEnv()->CallStaticVoidMethod(
+        globalCallbackRef->env()->CallStaticVoidMethod(
                 gHardwareBufferRendererClassInfo.clazz,
                 gHardwareBufferRendererClassInfo.invokeRenderCallback, globalCallbackRef->object(),
                 reinterpret_cast<jint>(fd.release()), reinterpret_cast<jint>(status));
@@ -172,7 +172,8 @@
 int register_android_graphics_HardwareBufferRenderer(JNIEnv* env) {
     jclass hardwareBufferRendererClazz =
             FindClassOrDie(env, "android/graphics/HardwareBufferRenderer");
-    gHardwareBufferRendererClassInfo.clazz = hardwareBufferRendererClazz;
+    gHardwareBufferRendererClassInfo.clazz =
+            reinterpret_cast<jclass>(env->NewGlobalRef(hardwareBufferRendererClazz));
     gHardwareBufferRendererClassInfo.invokeRenderCallback =
             GetStaticMethodIDOrDie(env, hardwareBufferRendererClazz, "invokeRenderCallback",
                                    "(Ljava/util/function/Consumer;II)V");
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index d6aad7d..6a7411f 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -94,12 +94,21 @@
     jmethodID getDestinationBitmap;
 } gCopyRequest;
 
+static JNIEnv* getenv(JavaVM* vm) {
+    JNIEnv* env;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm);
+    }
+    return env;
+}
+
 typedef ANativeWindow* (*ANW_fromSurface)(JNIEnv* env, jobject surface);
 ANW_fromSurface fromSurface;
 
 class FrameCommitWrapper : public LightRefBase<FrameCommitWrapper> {
 public:
     explicit FrameCommitWrapper(JNIEnv* env, jobject jobject) {
+        env->GetJavaVM(&mVm);
         mObject = env->NewGlobalRef(jobject);
         LOG_ALWAYS_FATAL_IF(!mObject, "Failed to make global ref");
     }
@@ -109,18 +118,19 @@
     void onFrameCommit(bool didProduceBuffer) {
         if (mObject) {
             ATRACE_FORMAT("frameCommit success=%d", didProduceBuffer);
-            GraphicsJNI::getJNIEnv()->CallVoidMethod(mObject, gFrameCommitCallback.onFrameCommit,
-                                                     didProduceBuffer);
+            getenv(mVm)->CallVoidMethod(mObject, gFrameCommitCallback.onFrameCommit,
+                                        didProduceBuffer);
             releaseObject();
         }
     }
 
 private:
+    JavaVM* mVm;
     jobject mObject;
 
     void releaseObject() {
         if (mObject) {
-            GraphicsJNI::getJNIEnv()->DeleteGlobalRef(mObject);
+            getenv(mVm)->DeleteGlobalRef(mObject);
             mObject = nullptr;
         }
     }
@@ -541,10 +551,9 @@
         auto pictureState = std::make_shared<PictureCaptureState>();
         proxy->setPictureCapturedCallback([globalCallbackRef,
                                            pictureState](sk_sp<SkPicture>&& picture) {
-            JNIEnv* env = GraphicsJNI::getJNIEnv();
             Picture* wrapper = new PictureWrapper{std::move(picture), pictureState};
-            env->CallStaticVoidMethod(gHardwareRenderer.clazz,
-                    gHardwareRenderer.invokePictureCapturedCallback,
+            globalCallbackRef->env()->CallStaticVoidMethod(
+                    gHardwareRenderer.clazz, gHardwareRenderer.invokePictureCapturedCallback,
                     static_cast<jlong>(reinterpret_cast<intptr_t>(wrapper)),
                     globalCallbackRef->object());
         });
@@ -561,16 +570,14 @@
         LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
         auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(
                 vm, env->NewGlobalRef(aSurfaceTransactionCallback));
-        proxy->setASurfaceTransactionCallback(
-                [globalCallbackRef](int64_t transObj, int64_t scObj, int64_t frameNr) -> bool {
-                    JNIEnv* env = GraphicsJNI::getJNIEnv();
-                    jboolean ret = env->CallBooleanMethod(
-                            globalCallbackRef->object(),
-                            gASurfaceTransactionCallback.onMergeTransaction,
-                            static_cast<jlong>(transObj), static_cast<jlong>(scObj),
-                            static_cast<jlong>(frameNr));
-                    return ret;
-                });
+        proxy->setASurfaceTransactionCallback([globalCallbackRef](int64_t transObj, int64_t scObj,
+                                                                  int64_t frameNr) -> bool {
+            jboolean ret = globalCallbackRef->env()->CallBooleanMethod(
+                    globalCallbackRef->object(), gASurfaceTransactionCallback.onMergeTransaction,
+                    static_cast<jlong>(transObj), static_cast<jlong>(scObj),
+                    static_cast<jlong>(frameNr));
+            return ret;
+        });
     }
 }
 
@@ -585,9 +592,8 @@
         auto globalCallbackRef =
                 std::make_shared<JGlobalRefHolder>(vm, env->NewGlobalRef(callback));
         proxy->setPrepareSurfaceControlForWebviewCallback([globalCallbackRef]() {
-            JNIEnv* env = GraphicsJNI::getJNIEnv();
-            env->CallVoidMethod(globalCallbackRef->object(),
-                                gPrepareSurfaceControlForWebviewCallback.prepare);
+            globalCallbackRef->env()->CallVoidMethod(
+                    globalCallbackRef->object(), gPrepareSurfaceControlForWebviewCallback.prepare);
         });
     }
 }
@@ -604,7 +610,7 @@
                 env->NewGlobalRef(frameCallback));
         proxy->setFrameCallback([globalCallbackRef](int32_t syncResult,
                                                     int64_t frameNr) -> std::function<void(bool)> {
-            JNIEnv* env = GraphicsJNI::getJNIEnv();
+            JNIEnv* env = globalCallbackRef->env();
             ScopedLocalRef<jobject> frameCommitCallback(
                     env, env->CallObjectMethod(
                                  globalCallbackRef->object(), gFrameDrawingCallback.onFrameDraw,
@@ -643,9 +649,8 @@
         auto globalCallbackRef =
                 std::make_shared<JGlobalRefHolder>(vm, env->NewGlobalRef(callback));
         proxy->setFrameCompleteCallback([globalCallbackRef]() {
-            JNIEnv* env = GraphicsJNI::getJNIEnv();
-            env->CallVoidMethod(globalCallbackRef->object(),
-                                gFrameCompleteCallback.onFrameComplete);
+            globalCallbackRef->env()->CallVoidMethod(globalCallbackRef->object(),
+                                                     gFrameCompleteCallback.onFrameComplete);
         });
     }
 }
@@ -656,8 +661,7 @@
             : CopyRequest(srcRect), mRefHolder(vm, jCopyRequest) {}
 
     virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) override {
-        JNIEnv* env = GraphicsJNI::getJNIEnv();
-        jlong bitmapPtr = env->CallLongMethod(
+        jlong bitmapPtr = mRefHolder.env()->CallLongMethod(
                 mRefHolder.object(), gCopyRequest.getDestinationBitmap, srcWidth, srcHeight);
         SkBitmap bitmap;
         bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap);
@@ -665,9 +669,8 @@
     }
 
     virtual void onCopyFinished(CopyResult result) override {
-        JNIEnv* env = GraphicsJNI::getJNIEnv();
-        env->CallVoidMethod(mRefHolder.object(), gCopyRequest.onCopyFinished,
-                            static_cast<jint>(result));
+        mRefHolder.env()->CallVoidMethod(mRefHolder.object(), gCopyRequest.onCopyFinished,
+                                         static_cast<jint>(result));
     }
 
 private:
diff --git a/libs/hwui/jni/android_graphics_Mesh.cpp b/libs/hwui/jni/android_graphics_Mesh.cpp
new file mode 100644
index 0000000..04339dc
--- /dev/null
+++ b/libs/hwui/jni/android_graphics_Mesh.cpp
@@ -0,0 +1,350 @@
+/*
+ * 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.
+ */
+
+#include <GrDirectContext.h>
+#include <Mesh.h>
+#include <SkMesh.h>
+#include <jni.h>
+#include <log/log.h>
+
+#include <utility>
+
+#include "GraphicsJNI.h"
+#include "graphics_jni_helpers.h"
+
+#define gIndexByteSize 2
+
+// A smart pointer that provides read only access to Java.nio.Buffer. This handles both
+// direct and indrect buffers, allowing access to the underlying data in both
+// situations. If passed a null buffer, we will throw NullPointerException,
+// and c_data will return nullptr.
+//
+// This class draws from com_google_android_gles_jni_GLImpl.cpp for Buffer to void *
+// conversion.
+class ScopedJavaNioBuffer {
+public:
+    ScopedJavaNioBuffer(JNIEnv* env, jobject buffer, size_t size, jboolean isDirect)
+            : mEnv(env), mBuffer(buffer) {
+        if (buffer == nullptr) {
+            mDataBase = nullptr;
+            mData = nullptr;
+            jniThrowNullPointerException(env);
+        } else {
+            mArray = (jarray) nullptr;
+            if (isDirect) {
+                mData = getDirectBufferPointer(mEnv, mBuffer);
+            } else {
+                mData = setIndirectData(size);
+            }
+        }
+    }
+
+    ScopedJavaNioBuffer(ScopedJavaNioBuffer&& rhs) noexcept { *this = std::move(rhs); }
+
+    ~ScopedJavaNioBuffer() { reset(); }
+
+    void reset() {
+        if (mDataBase) {
+            releasePointer(mEnv, mArray, mDataBase, JNI_FALSE);
+            mDataBase = nullptr;
+        }
+    }
+
+    ScopedJavaNioBuffer& operator=(ScopedJavaNioBuffer&& rhs) noexcept {
+        if (this != &rhs) {
+            reset();
+
+            mEnv = rhs.mEnv;
+            mBuffer = rhs.mBuffer;
+            mDataBase = rhs.mDataBase;
+            mData = rhs.mData;
+            mArray = rhs.mArray;
+            rhs.mEnv = nullptr;
+            rhs.mData = nullptr;
+            rhs.mBuffer = nullptr;
+            rhs.mArray = nullptr;
+            rhs.mDataBase = nullptr;
+        }
+        return *this;
+    }
+
+    const void* data() const { return mData; }
+
+private:
+    /**
+     * This code is taken and modified from com_google_android_gles_jni_GLImpl.cpp to extract data
+     * from a java.nio.Buffer.
+     */
+    void* getDirectBufferPointer(JNIEnv* env, jobject buffer) {
+        if (buffer == nullptr) {
+            return nullptr;
+        }
+
+        jint position;
+        jint limit;
+        jint elementSizeShift;
+        jlong pointer;
+        pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
+        if (pointer == 0) {
+            jniThrowException(mEnv, "java/lang/IllegalArgumentException",
+                              "Must use a native order direct Buffer");
+            return nullptr;
+        }
+        pointer += position << elementSizeShift;
+        return reinterpret_cast<void*>(pointer);
+    }
+
+    static void releasePointer(JNIEnv* env, jarray array, void* data, jboolean commit) {
+        env->ReleasePrimitiveArrayCritical(array, data, commit ? 0 : JNI_ABORT);
+    }
+
+    static void* getPointer(JNIEnv* env, jobject buffer, jarray* array, jint* remaining,
+                            jint* offset) {
+        jint position;
+        jint limit;
+        jint elementSizeShift;
+
+        jlong pointer;
+        pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
+        *remaining = (limit - position) << elementSizeShift;
+        if (pointer != 0L) {
+            *array = nullptr;
+            pointer += position << elementSizeShift;
+            return reinterpret_cast<void*>(pointer);
+        }
+
+        *array = jniGetNioBufferBaseArray(env, buffer);
+        *offset = jniGetNioBufferBaseArrayOffset(env, buffer);
+        return nullptr;
+    }
+
+    /**
+     * This is a copy of
+     * static void android_glBufferData__IILjava_nio_Buffer_2I
+     * from com_google_android_gles_jni_GLImpl.cpp
+     */
+    void* setIndirectData(size_t size) {
+        jint exception;
+        const char* exceptionType;
+        const char* exceptionMessage;
+        jint bufferOffset = (jint)0;
+        jint remaining;
+        void* tempData;
+
+        if (mBuffer) {
+            tempData =
+                    (void*)getPointer(mEnv, mBuffer, (jarray*)&mArray, &remaining, &bufferOffset);
+            if (remaining < size) {
+                exception = 1;
+                exceptionType = "java/lang/IllegalArgumentException";
+                exceptionMessage = "remaining() < size < needed";
+                goto exit;
+            }
+        }
+        if (mBuffer && tempData == nullptr) {
+            mDataBase = (char*)mEnv->GetPrimitiveArrayCritical(mArray, (jboolean*)0);
+            tempData = (void*)(mDataBase + bufferOffset);
+        }
+        return tempData;
+    exit:
+        if (mArray) {
+            releasePointer(mEnv, mArray, (void*)(mDataBase), JNI_FALSE);
+        }
+        if (exception) {
+            jniThrowException(mEnv, exceptionType, exceptionMessage);
+        }
+        return nullptr;
+    }
+
+    JNIEnv* mEnv;
+
+    // Java Buffer data
+    void* mData;
+    jobject mBuffer;
+
+    // Indirect Buffer Data
+    jarray mArray;
+    char* mDataBase;
+};
+
+namespace android {
+
+static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
+                  jboolean isDirect, jint vertexCount, jint vertexOffset, jfloat left, jfloat top,
+                  jfloat right, jfloat bottom) {
+    auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
+    size_t bufferSize = vertexCount * skMeshSpec->stride();
+    auto buff = ScopedJavaNioBuffer(env, vertexBuffer, bufferSize, isDirect);
+    auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
+    auto meshPtr = new Mesh(skMeshSpec, mode, buff.data(), bufferSize, vertexCount, vertexOffset,
+                            std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
+    auto [valid, msg] = meshPtr->validate();
+    if (!valid) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
+    }
+    return reinterpret_cast<jlong>(meshPtr);
+}
+
+static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
+                         jboolean isVertexDirect, jint vertexCount, jint vertexOffset,
+                         jobject indexBuffer, jboolean isIndexDirect, jint indexCount,
+                         jint indexOffset, jfloat left, jfloat top, jfloat right, jfloat bottom) {
+    auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
+    auto vertexBufferSize = vertexCount * skMeshSpec->stride();
+    auto indexBufferSize = indexCount * gIndexByteSize;
+    auto vBuf = ScopedJavaNioBuffer(env, vertexBuffer, vertexBufferSize, isVertexDirect);
+    auto iBuf = ScopedJavaNioBuffer(env, indexBuffer, indexBufferSize, isIndexDirect);
+    auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
+    auto meshPtr = new Mesh(skMeshSpec, mode, vBuf.data(), vertexBufferSize, vertexCount,
+                            vertexOffset, iBuf.data(), indexBufferSize, indexCount, indexOffset,
+                            std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
+    auto [valid, msg] = meshPtr->validate();
+    if (!valid) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
+    }
+
+    return reinterpret_cast<jlong>(meshPtr);
+}
+
+static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) {
+    va_list args;
+    va_start(args, fmt);
+    int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args);
+    va_end(args);
+    return ret;
+}
+
+static bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) {
+    switch (type) {
+        case SkRuntimeEffect::Uniform::Type::kFloat:
+        case SkRuntimeEffect::Uniform::Type::kFloat2:
+        case SkRuntimeEffect::Uniform::Type::kFloat3:
+        case SkRuntimeEffect::Uniform::Type::kFloat4:
+        case SkRuntimeEffect::Uniform::Type::kFloat2x2:
+        case SkRuntimeEffect::Uniform::Type::kFloat3x3:
+        case SkRuntimeEffect::Uniform::Type::kFloat4x4:
+            return false;
+        case SkRuntimeEffect::Uniform::Type::kInt:
+        case SkRuntimeEffect::Uniform::Type::kInt2:
+        case SkRuntimeEffect::Uniform::Type::kInt3:
+        case SkRuntimeEffect::Uniform::Type::kInt4:
+            return true;
+    }
+}
+
+static void nativeUpdateFloatUniforms(JNIEnv* env, MeshUniformBuilder* builder,
+                                      const char* uniformName, const float values[], int count,
+                                      bool isColor) {
+    MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName);
+    if (uniform.fVar == nullptr) {
+        ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+    } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) {
+        if (isColor) {
+            jniThrowExceptionFmt(
+                    env, "java/lang/IllegalArgumentException",
+                    "attempting to set a color uniform using the non-color specific APIs: %s %x",
+                    uniformName, uniform.fVar->flags);
+        } else {
+            ThrowIAEFmt(env,
+                        "attempting to set a non-color uniform using the setColorUniform APIs: %s",
+                        uniformName);
+        }
+    } else if (isIntUniformType(uniform.fVar->type)) {
+        ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s",
+                    uniformName);
+    } else if (!uniform.set<float>(values, count)) {
+        ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
+                    uniform.fVar->sizeInBytes(), sizeof(float) * count);
+    }
+}
+
+static void updateFloatUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
+                                jfloat value1, jfloat value2, jfloat value3, jfloat value4,
+                                jint count) {
+    auto* wrapper = reinterpret_cast<Mesh*>(meshWrapper);
+    ScopedUtfChars name(env, uniformName);
+    const float values[4] = {value1, value2, value3, value4};
+    nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count, false);
+    wrapper->markDirty();
+}
+
+static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName,
+                                     jfloatArray jvalues, jboolean isColor) {
+    auto wrapper = reinterpret_cast<Mesh*>(meshWrapper);
+    ScopedUtfChars name(env, jUniformName);
+    AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
+    nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
+                              autoValues.length(), isColor);
+    wrapper->markDirty();
+}
+
+static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
+                                    const char* uniformName, const int values[], int count) {
+    MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName);
+    if (uniform.fVar == nullptr) {
+        ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+    } else if (!isIntUniformType(uniform.fVar->type)) {
+        ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s",
+                    uniformName);
+    } else if (!uniform.set<int>(values, count)) {
+        ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
+                    uniform.fVar->sizeInBytes(), sizeof(float) * count);
+    }
+}
+
+static void updateIntUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
+                              jint value1, jint value2, jint value3, jint value4, jint count) {
+    auto wrapper = reinterpret_cast<Mesh*>(meshWrapper);
+    ScopedUtfChars name(env, uniformName);
+    const int values[4] = {value1, value2, value3, value4};
+    nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count);
+    wrapper->markDirty();
+}
+
+static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
+                                   jintArray values) {
+    auto wrapper = reinterpret_cast<Mesh*>(meshWrapper);
+    ScopedUtfChars name(env, uniformName);
+    AutoJavaIntArray autoValues(env, values, 0);
+    nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
+                            autoValues.length());
+    wrapper->markDirty();
+}
+
+static void MeshWrapper_destroy(Mesh* wrapper) {
+    delete wrapper;
+}
+
+static jlong getMeshFinalizer(JNIEnv*, jobject) {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshWrapper_destroy));
+}
+
+static const JNINativeMethod gMeshMethods[] = {
+        {"nativeGetFinalizer", "()J", (void*)getMeshFinalizer},
+        {"nativeMake", "(JILjava/nio/Buffer;ZIIFFFF)J", (void*)make},
+        {"nativeMakeIndexed", "(JILjava/nio/Buffer;ZIILjava/nio/ShortBuffer;ZIIFFFF)J",
+         (void*)makeIndexed},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V", (void*)updateFloatArrayUniforms},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V", (void*)updateFloatUniforms},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V", (void*)updateIntArrayUniforms},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V", (void*)updateIntUniforms}};
+
+int register_android_graphics_Mesh(JNIEnv* env) {
+    android::RegisterMethodsOrDie(env, "android/graphics/Mesh", gMeshMethods, NELEM(gMeshMethods));
+    return 0;
+}
+
+}  // namespace android
\ No newline at end of file
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index fcfc4f8..af2d3b3 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -23,6 +23,7 @@
 #else
 #include "DamageAccumulator.h"
 #endif
+#include "TreeInfo.h"
 #include "VectorDrawable.h"
 #ifdef __ANDROID__
 #include "renderthread/CanvasContext.h"
@@ -102,6 +103,12 @@
         info.prepareTextures = false;
         info.canvasContext.unpinImages();
     }
+
+    auto grContext = info.canvasContext.getGrContext();
+    for (auto mesh : mMeshes) {
+        mesh->updateSkMesh(grContext);
+    }
+
 #endif
 
     bool hasBackwardProjectedNodesHere = false;
@@ -168,6 +175,7 @@
 
     mDisplayList.reset();
 
+    mMeshes.clear();
     mMutableImages.clear();
     mVectorDrawables.clear();
     mAnimatedImages.clear();
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index 2a67734..7af31a4 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -18,6 +18,7 @@
 
 #include <deque>
 
+#include "Mesh.h"
 #include "RecordingCanvas.h"
 #include "RenderNodeDrawable.h"
 #include "TreeInfo.h"
@@ -167,6 +168,7 @@
     std::deque<RenderNodeDrawable> mChildNodes;
     std::deque<FunctorDrawable*> mChildFunctors;
     std::vector<SkImage*> mMutableImages;
+    std::vector<const Mesh*> mMeshes;
 
 private:
     std::vector<Pair<VectorDrawableRoot*, SkMatrix>> mVectorDrawables;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index e1c8877..3ca7eeb 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -321,6 +321,11 @@
     return 0;
 }
 
+void SkiaRecordingCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) {
+    mDisplayList->mMeshes.push_back(&mesh);
+    mRecorder.drawMesh(mesh, blender, paint);
+}
+
 }  // namespace skiapipeline
 }  // namespace uirenderer
 }  // namespace android
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index 3fd8fa3..a8e4580 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -81,6 +81,7 @@
     virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
 
     virtual void enableZ(bool enableZ) override;
+    virtual void drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) override;
     virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) override;
     virtual void drawRenderNode(uirenderer::RenderNode* renderNode) override;
 
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 5136675..4da4c7f 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -16,9 +16,9 @@
 
 package android.media;
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.content.Context.DEVICE_ID_DEFAULT;
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index b6ab262..7faa13c 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -16,9 +16,9 @@
 
 package android.media;
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
 
 import android.annotation.CallbackExecutor;
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
index 38115e1..3f44b09 100644
--- a/media/java/android/media/PlayerBase.java
+++ b/media/java/android/media/PlayerBase.java
@@ -16,9 +16,9 @@
 
 package android.media;
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
 
 import android.annotation.NonNull;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java
index 76543f4..6089f42 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java
@@ -17,10 +17,10 @@
 package com.android.mediaframeworktest.unit;
 
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.media.AudioManager.FX_KEY_CLICK;
 
 import static org.mockito.ArgumentMatchers.anyInt;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java
index 9c813c2..71228e2 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java
@@ -16,10 +16,10 @@
 
 package com.android.mediaframeworktest.unit;
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
 
 import static org.junit.Assert.assertEquals;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioTrackUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioTrackUnitTest.java
index ffed39a..ac24a10 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioTrackUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioTrackUnitTest.java
@@ -16,10 +16,10 @@
 
 package com.android.mediaframeworktest.unit;
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
 
 import static org.junit.Assert.assertEquals;
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 89ce451..3d718a2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -23,6 +23,7 @@
 import android.credentials.CredentialOption
 import android.credentials.GetCredentialRequest
 import android.credentials.ui.AuthenticationEntry
+import android.credentials.ui.CancelUiRequest
 import android.credentials.ui.Constants
 import android.credentials.ui.Entry
 import android.credentials.ui.CreateCredentialProviderData
@@ -72,6 +73,12 @@
             RequestInfo::class.java
         ) ?: testCreatePasswordRequestInfo()
 
+        val originName: String? = when (requestInfo.type) {
+            RequestInfo.TYPE_CREATE -> requestInfo.createCredentialRequest?.origin
+            RequestInfo.TYPE_GET -> requestInfo.getCredentialRequest?.origin
+            else -> null
+        }
+
         providerEnabledList = when (requestInfo.type) {
             RequestInfo.TYPE_CREATE ->
                 intent.extras?.getParcelableArrayList(
@@ -105,14 +112,15 @@
                 val isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
                 val providerEnableListUiState = getCreateProviderEnableListInitialUiState()
                 val providerDisableListUiState = getCreateProviderDisableListInitialUiState()
-                val requestDisplayInfoUiState = getCreateRequestDisplayInfoInitialUiState()!!
+                val requestDisplayInfoUiState =
+                    getCreateRequestDisplayInfoInitialUiState(originName)!!
                 UiState(
                     createCredentialUiState = CreateFlowUtils.toCreateCredentialUiState(
                         providerEnableListUiState,
                         providerDisableListUiState,
                         defaultProviderId,
                         requestDisplayInfoUiState,
-                        /** isOnPasskeyIntroStateAlready = */
+                        /** isOnPasskeyIntroStateAlready */
                         false,
                         isPasskeyFirstUse
                     )!!,
@@ -121,7 +129,7 @@
             }
             RequestInfo.TYPE_GET -> UiState(
                 createCredentialUiState = null,
-                getCredentialUiState = getCredentialInitialUiState()!!,
+                getCredentialUiState = getCredentialInitialUiState(originName)!!,
             )
             else -> throw IllegalStateException("Unrecognized request type: ${requestInfo.type}")
         }
@@ -172,11 +180,11 @@
     }
 
     // IMPORTANT: new invocation should be mindful that this method can throw.
-    private fun getCredentialInitialUiState(): GetCredentialUiState? {
+    private fun getCredentialInitialUiState(originName: String?): GetCredentialUiState? {
         val providerEnabledList = GetFlowUtils.toProviderList(
             providerEnabledList as List<GetCredentialProviderData>, context
         )
-        val requestDisplayInfo = GetFlowUtils.toRequestDisplayInfo(requestInfo, context)
+        val requestDisplayInfo = GetFlowUtils.toRequestDisplayInfo(requestInfo, context, originName)
         return GetCredentialUiState(
             providerEnabledList,
             requestDisplayInfo ?: return null,
@@ -198,8 +206,10 @@
         )
     }
 
-    private fun getCreateRequestDisplayInfoInitialUiState(): RequestDisplayInfo? {
-        return CreateFlowUtils.toRequestDisplayInfo(requestInfo, context)
+    private fun getCreateRequestDisplayInfoInitialUiState(
+        originName: String?
+    ): RequestDisplayInfo? {
+        return CreateFlowUtils.toRequestDisplayInfo(requestInfo, context, originName)
     }
 
     companion object {
@@ -214,6 +224,14 @@
                 resultReceiver.send(cancelCode, resultData)
             }
         }
+
+        /** Return the request token whose UI should be cancelled, or null otherwise. */
+        fun getCancelUiRequestToken(intent: Intent): IBinder? {
+            return intent.extras?.getParcelable(
+                CancelUiRequest.EXTRA_CANCEL_UI_REQUEST,
+                CancelUiRequest::class.java
+            )?.token
+        }
     }
 
     // TODO: below are prototype functionalities. To be removed for productionization.
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index a3e4c81..e8e3974 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -47,6 +47,12 @@
         super.onCreate(savedInstanceState)
         Log.d(Constants.LOG_TAG, "Creating new CredentialSelectorActivity")
         try {
+            if (CredentialManagerRepo.getCancelUiRequestToken(intent) != null) {
+                Log.d(
+                    Constants.LOG_TAG, "Received UI cancellation intent; cancelling the activity.")
+                this.finish()
+                return
+            }
             val userConfigRepo = UserConfigRepo(this)
             val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo)
             setContent {
@@ -67,10 +73,19 @@
         setIntent(intent)
         Log.d(Constants.LOG_TAG, "Existing activity received new intent")
         try {
-            val userConfigRepo = UserConfigRepo(this)
-            val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo)
+            val cancelUiRequestToken = CredentialManagerRepo.getCancelUiRequestToken(intent)
             val viewModel: CredentialSelectorViewModel by viewModels()
-            viewModel.onNewCredentialManagerRepo(credManRepo)
+            if (cancelUiRequestToken != null &&
+                viewModel.shouldCancelCurrentUi(cancelUiRequestToken)) {
+                Log.d(
+                    Constants.LOG_TAG, "Received UI cancellation intent; cancelling the activity.")
+                this.finish()
+                return
+            } else {
+                val userConfigRepo = UserConfigRepo(this)
+                val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo)
+                viewModel.onNewCredentialManagerRepo(credManRepo)
+            }
         } catch (e: Exception) {
             onInitializationError(e, intent)
         }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index a1e0823..9b7139c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -17,6 +17,7 @@
 package com.android.credentialmanager
 
 import android.app.Activity
+import android.os.IBinder
 import android.util.Log
 import androidx.activity.compose.ManagedActivityResultLauncher
 import androidx.activity.result.ActivityResult
@@ -135,6 +136,11 @@
         uiState = uiState.copy(dialogState = DialogState.COMPLETE)
     }
 
+    /** Return true if the current UI's request token matches the UI cancellation request token. */
+    fun shouldCancelCurrentUi(cancelRequestToken: IBinder): Boolean {
+        return credManRepo.requestInfo.token.equals(cancelRequestToken)
+    }
+
     /**************************************************************************/
     /*****                      Get Flow Callbacks                        *****/
     /**************************************************************************/
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index ccfc132..9c9c2a3 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -170,9 +170,11 @@
         fun toRequestDisplayInfo(
             requestInfo: RequestInfo,
             context: Context,
+            originName: String?,
         ): com.android.credentialmanager.getflow.RequestDisplayInfo? {
             return com.android.credentialmanager.getflow.RequestDisplayInfo(
-                appName = getAppLabel(context.packageManager, requestInfo.appPackageName)
+                appName = originName
+                    ?: getAppLabel(context.packageManager, requestInfo.appPackageName)
                     ?: return null
             )
         }
@@ -395,8 +397,10 @@
         fun toRequestDisplayInfo(
             requestInfo: RequestInfo,
             context: Context,
+            originName: String?,
         ): RequestDisplayInfo? {
-            val appLabel = getAppLabel(context.packageManager, requestInfo.appPackageName)
+            val appLabel = originName
+                ?: getAppLabel(context.packageManager, requestInfo.appPackageName)
                 ?: return null
             val createCredentialRequest = requestInfo.createCredentialRequest ?: return null
             val createCredentialRequestJetpack = CreateCredentialRequest.createFrom(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/DialogResult.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/DialogResult.kt
index 6d07df7..2971433 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/DialogResult.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/DialogResult.kt
@@ -24,8 +24,6 @@
 
 enum class ResultState {
   COMPLETE,
-  NORMAL_CANCELED,
-  LAUNCH_SETTING_CANCELED
 }
 
 data class DialogResult(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt
index b94840f..04a2c07 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt
@@ -16,6 +16,7 @@
 
 package com.android.credentialmanager.common.ui
 
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.padding
 import com.android.credentialmanager.R
 import androidx.compose.material.Icon
@@ -33,7 +34,6 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 
 @Composable
 fun ActionButton(text: String, onClick: () -> Unit) {
@@ -42,12 +42,10 @@
         onClick = onClick,
         colors = ButtonDefaults.textButtonColors(
             contentColor = MaterialTheme.colorScheme.primary,
-        )
+        ),
+        contentPadding = PaddingValues(start = 12.dp, top = 10.dp, end = 12.dp, bottom = 10.dp),
     ) {
-        LargeLabelText(
-            text = text,
-            modifier = Modifier.padding(vertical = 10.dp, horizontal = 12.dp),
-        )
+        LargeLabelText(text = text)
     }
 }
 
@@ -69,7 +67,7 @@
             contentDescription = if (toggleState.value)
                 stringResource(R.string.content_description_show_password) else
                 stringResource(R.string.content_description_hide_password),
-            tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant
+            tint = MaterialTheme.colorScheme.onSurfaceVariant,
         )
     }
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt
index 8f48f6b..c09a692 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt
@@ -16,6 +16,7 @@
 
 package com.android.credentialmanager.common.ui
 
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.FilledTonalButton
@@ -33,11 +34,9 @@
         colors = ButtonDefaults.filledTonalButtonColors(
             containerColor = MaterialTheme.colorScheme.primary,
             contentColor = MaterialTheme.colorScheme.onPrimary,
-        )
+        ),
+        contentPadding = PaddingValues(start = 24.dp, top = 10.dp, end = 24.dp, bottom = 10.dp),
     ) {
-        LargeLabelText(
-            text = text,
-            modifier = Modifier.padding(vertical = 10.dp, horizontal = 24.dp),
-        )
+        LargeLabelText(text = text)
     }
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt
index 8061da7..514ff90 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt
@@ -75,7 +75,7 @@
                         action()
                     }
                     IconButton(onClick = onDismiss, modifier = Modifier.padding(
-                        top = 18.dp, bottom = 18.dp, start = 16.dp, end = 24.dp,
+                        top = 4.dp, bottom = 4.dp, start = 2.dp, end = 10.dp,
                     )) {
                         Icon(
                             Icons.Filled.Close,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
index 8f7c37e..8af729e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
@@ -16,7 +16,7 @@
 
 package com.android.credentialmanager.common.ui
 
-import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
@@ -32,7 +32,7 @@
 @Composable
 fun HeadlineText(text: String, modifier: Modifier = Modifier) {
     Text(
-        modifier = modifier.wrapContentHeight(),
+        modifier = modifier.wrapContentSize(),
         text = text,
         color = MaterialTheme.colorScheme.onSurface,
         textAlign = TextAlign.Center,
@@ -46,7 +46,7 @@
 @Composable
 fun BodyMediumText(text: String, modifier: Modifier = Modifier) {
     Text(
-        modifier = modifier.wrapContentHeight(),
+        modifier = modifier.wrapContentSize(),
         text = text,
         color = MaterialTheme.colorScheme.onSurfaceVariant,
         style = MaterialTheme.typography.bodyMedium,
@@ -59,7 +59,7 @@
 @Composable
 fun BodySmallText(text: String, modifier: Modifier = Modifier) {
     Text(
-        modifier = modifier.wrapContentHeight(),
+        modifier = modifier.wrapContentSize(),
         text = text,
         color = MaterialTheme.colorScheme.onSurfaceVariant,
         style = MaterialTheme.typography.bodySmall,
@@ -72,7 +72,7 @@
 @Composable
 fun LargeTitleText(text: String, modifier: Modifier = Modifier) {
     Text(
-        modifier = modifier.wrapContentHeight(),
+        modifier = modifier.wrapContentSize(),
         text = text,
         color = MaterialTheme.colorScheme.onSurface,
         style = MaterialTheme.typography.titleLarge,
@@ -85,7 +85,7 @@
 @Composable
 fun SmallTitleText(text: String, modifier: Modifier = Modifier) {
     Text(
-        modifier = modifier.wrapContentHeight(),
+        modifier = modifier.wrapContentSize(),
         text = text,
         color = MaterialTheme.colorScheme.onSurface,
         style = MaterialTheme.typography.titleSmall,
@@ -98,7 +98,7 @@
 @Composable
 fun SectionHeaderText(text: String, modifier: Modifier = Modifier, color: Color) {
     Text(
-        modifier = modifier.wrapContentHeight(),
+        modifier = modifier.wrapContentSize(),
         text = text,
         color = color,
         style = MaterialTheme.typography.titleSmall,
@@ -111,7 +111,7 @@
 @Composable
 fun SnackbarContentText(text: String, modifier: Modifier = Modifier) {
     Text(
-        modifier = modifier.wrapContentHeight(),
+        modifier = modifier.wrapContentSize(),
         text = text,
         color = MaterialTheme.colorScheme.inverseOnSurface,
         style = MaterialTheme.typography.bodyMedium,
@@ -124,7 +124,7 @@
 @Composable
 fun SnackbarActionText(text: String, modifier: Modifier = Modifier) {
     Text(
-        modifier = modifier.wrapContentHeight(),
+        modifier = modifier.wrapContentSize(),
         text = text,
         color = MaterialTheme.colorScheme.inversePrimary,
         style = MaterialTheme.typography.labelLarge,
@@ -137,7 +137,7 @@
 @Composable
 fun LargeLabelTextOnSurfaceVariant(text: String, modifier: Modifier = Modifier) {
     Text(
-        modifier = modifier.wrapContentHeight(),
+        modifier = modifier.wrapContentSize(),
         text = text,
         textAlign = TextAlign.Center,
         color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -151,7 +151,7 @@
 @Composable
 fun LargeLabelText(text: String, modifier: Modifier = Modifier) {
     Text(
-        modifier = modifier.wrapContentHeight(),
+        modifier = modifier.wrapContentSize(),
         text = text,
         textAlign = TextAlign.Center,
         style = MaterialTheme.typography.labelLarge,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 54f8e5c..92a6c39 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -22,7 +22,9 @@
 import androidx.activity.result.IntentSenderRequest
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.lazy.LazyColumn
@@ -454,13 +456,13 @@
     Snackbar(
         action = {
             TextButton(
-                modifier = Modifier.padding(top = 12.dp, bottom = 12.dp, start = 16.dp),
+                modifier = Modifier.padding(top = 4.dp, bottom = 4.dp, start = 16.dp)
+                    .heightIn(min = 32.dp),
                 onClick = { onClick(true) },
+                contentPadding =
+                PaddingValues(start = 0.dp, top = 6.dp, end = 0.dp, bottom = 6.dp),
             ) {
-                SnackbarActionText(
-                    text = stringResource(R.string.snackbar_action),
-                    Modifier.padding(vertical = 6.dp)
-                )
+                SnackbarActionText(text = stringResource(R.string.snackbar_action))
             }
         },
         onDismiss = onCancel,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 02e7557..d1f0f81 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -141,7 +141,7 @@
     entrySubkey,
     pendingIntent,
     fillInIntent,
-    shouldTerminateUiUponSuccessfulProviderResult = false,
+    shouldTerminateUiUponSuccessfulProviderResult = true,
 )
 
 data class RequestDisplayInfo(
diff --git a/packages/DynamicSystemInstallationService/AndroidManifest.xml b/packages/DynamicSystemInstallationService/AndroidManifest.xml
index b194738..c2aaeac 100644
--- a/packages/DynamicSystemInstallationService/AndroidManifest.xml
+++ b/packages/DynamicSystemInstallationService/AndroidManifest.xml
@@ -21,12 +21,7 @@
             android:exported="true"
             android:permission="android.permission.INSTALL_DYNAMIC_SYSTEM"
             android:foregroundServiceType="systemExempted"
-            android:process=":dynsystem">
-            <intent-filter>
-                <action android:name="android.os.image.action.NOTIFY_IF_IN_USE" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </service>
+            android:process=":dynsystem" />
 
         <activity android:name=".VerificationActivity"
             android:exported="true"
diff --git a/packages/SettingsLib/DeviceStateRotationLock/OWNERS b/packages/SettingsLib/DeviceStateRotationLock/OWNERS
new file mode 100644
index 0000000..091df26
--- /dev/null
+++ b/packages/SettingsLib/DeviceStateRotationLock/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/packages/SettingsLib/src/com/android/settingslib/devicestate/OWNERS
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/OWNERS b/packages/SettingsLib/Spa/OWNERS
index 2887872..464328e 100644
--- a/packages/SettingsLib/Spa/OWNERS
+++ b/packages/SettingsLib/Spa/OWNERS
@@ -4,3 +4,6 @@
 hanxu@google.com
 kellyz@google.com
 pierreqian@google.com
+lijun@google.com
+songchenxi@google.com
+cyl@google.com
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 888b09f..e846480 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -575,9 +575,15 @@
 
     /** Get the corresponding adaptive icon drawable. */
     public static Drawable getBadgedIcon(Context context, Drawable icon, UserHandle user) {
+        UserManager um = context.getSystemService(UserManager.class);
+        boolean isClone = um.getProfiles(user.getIdentifier()).stream()
+                .anyMatch(profile ->
+                        profile.isCloneProfile() && profile.id == user.getIdentifier());
         try (IconFactory iconFactory = IconFactory.obtain(context)) {
             return iconFactory
-                    .createBadgedIconBitmap(icon, new IconOptions().setUser(user))
+                    .createBadgedIconBitmap(
+                            icon,
+                            new IconOptions().setUser(user).setIsCloneProfile(isClone))
                     .newIcon(context);
         }
     }
diff --git a/packages/SystemUI/res-keyguard/layout/fsi_chrome_view.xml b/packages/SystemUI/res-keyguard/layout/fsi_chrome_view.xml
deleted file mode 100644
index 4ff2967..0000000
--- a/packages/SystemUI/res-keyguard/layout/fsi_chrome_view.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<com.android.systemui.statusbar.notification.fsi.FsiChromeView android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:layout_margin="50dp"
-    android:orientation="vertical"
-    xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:id="@+id/fsi_chrome"
-        android:layout_height="50dp"
-        android:orientation="horizontal">
-
-        <ImageView
-            android:id="@+id/fsi_app_icon"
-            android:layout_width="50dp"
-            android:layout_height="match_parent"
-            android:contentDescription="@null" />
-
-        <TextView
-            android:id="@+id/fsi_app_name"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:padding="10dp"
-            android:textSize="22dp"
-            android:gravity="center"
-            android:textColor="#FFFFFF"
-            android:text="AppName" />
-
-        <Space
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:layout_weight="1" />
-
-        <Button
-            android:id="@+id/fsi_fullscreen_button"
-            android:layout_width="100dp"
-            android:layout_height="match_parent"
-            android:text="fullscreen" />
-
-        <Button
-            android:id="@+id/fsi_dismiss_button"
-            android:layout_width="100dp"
-            android:layout_height="match_parent"
-            android:text="dismiss" />
-
-    </LinearLayout>
-
-</com.android.systemui.statusbar.notification.fsi.FsiChromeView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ac07d564..76f6f8a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -257,9 +257,6 @@
     <!-- Radius for notifications corners with adjacent notifications -->
     <dimen name="notification_corner_radius_small">4dp</dimen>
 
-    <!-- Vertical padding of the FSI container -->
-    <dimen name="fsi_chrome_vertical_padding">80dp</dimen>
-
     <!-- the padding of the shelf icon container -->
     <dimen name="shelf_icon_container_padding">13dp</dimen>
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index b53b868..f4c5815 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -21,8 +21,6 @@
 
 import com.android.keyguard.KeyguardClockSwitch.ClockSize;
 import com.android.keyguard.logging.KeyguardLogger;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.ClockAnimations;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -62,7 +60,6 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             ConfigurationController configurationController,
             DozeParameters dozeParameters,
-            FeatureFlags featureFlags,
             ScreenOffAnimationController screenOffAnimationController,
             KeyguardLogger logger) {
         super(keyguardStatusView);
@@ -73,8 +70,6 @@
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
                 dozeParameters, screenOffAnimationController, /* animateYPos= */ true,
                 logger.getBuffer());
-        mKeyguardVisibilityHelper.setOcclusionTransitionFlagEnabled(
-                featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION));
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 7e48193..a678edc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -28,7 +28,6 @@
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -49,7 +48,6 @@
     private boolean mAnimateYPos;
     private boolean mKeyguardViewVisibilityAnimating;
     private boolean mLastOccludedState = false;
-    private boolean mIsUnoccludeTransitionFlagEnabled = false;
     private final AnimationProperties mAnimationProperties = new AnimationProperties();
     private final LogBuffer mLogBuffer;
 
@@ -77,10 +75,6 @@
         return mKeyguardViewVisibilityAnimating;
     }
 
-    public void setOcclusionTransitionFlagEnabled(boolean enabled) {
-        mIsUnoccludeTransitionFlagEnabled = enabled;
-    }
-
     /**
      * Set the visibility of a keyguard view based on some new state.
      */
@@ -156,24 +150,9 @@
                 // since it may need to be cancelled due to keyguard lifecycle events.
                 mScreenOffAnimationController.animateInKeyguard(
                         mView, mAnimateKeyguardStatusViewVisibleEndRunnable);
-            } else if (!mIsUnoccludeTransitionFlagEnabled && mLastOccludedState && !isOccluded) {
-                // An activity was displayed over the lock screen, and has now gone away
-                log("Unoccluded transition");
-                mView.setVisibility(View.VISIBLE);
-                mView.setAlpha(0f);
-
-                mView.animate()
-                        .setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP)
-                        .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                        .alpha(1f)
-                        .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable)
-                        .start();
             } else {
                 log("Direct set Visibility to VISIBLE");
                 mView.setVisibility(View.VISIBLE);
-                if (!mIsUnoccludeTransitionFlagEnabled) {
-                    mView.setAlpha(1f);
-                }
             }
         } else {
             log("Direct set Visibility to GONE");
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 9921b1f..b86d419 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -47,10 +47,7 @@
 import com.android.systemui.recents.Recents
 import com.android.systemui.settings.dagger.MultiUserUtilsModule
 import com.android.systemui.shortcut.ShortcutKeyDispatcher
-import com.android.systemui.statusbar.notification.fsi.FsiChromeRepo
 import com.android.systemui.statusbar.notification.InstantAppNotifier
-import com.android.systemui.statusbar.notification.fsi.FsiChromeViewModelFactory
-import com.android.systemui.statusbar.notification.fsi.FsiChromeViewBinder
 import com.android.systemui.statusbar.phone.KeyguardLiftController
 import com.android.systemui.stylus.StylusUsiPowerStartable
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
@@ -91,24 +88,6 @@
     @ClassKey(ClipboardListener::class)
     abstract fun bindClipboardListener(sysui: ClipboardListener): CoreStartable
 
-    /** Inject into FsiChromeRepo.  */
-    @Binds
-    @IntoMap
-    @ClassKey(FsiChromeRepo::class)
-    abstract fun bindFSIChromeRepo(sysui: FsiChromeRepo): CoreStartable
-
-    /** Inject into FsiChromeWindowViewModel.  */
-    @Binds
-    @IntoMap
-    @ClassKey(FsiChromeViewModelFactory::class)
-    abstract fun bindFSIChromeWindowViewModel(sysui: FsiChromeViewModelFactory): CoreStartable
-
-    /** Inject into FsiChromeWindowBinder.  */
-    @Binds
-    @IntoMap
-    @ClassKey(FsiChromeViewBinder::class)
-    abstract fun bindFsiChromeWindowBinder(sysui: FsiChromeViewBinder): CoreStartable
-
     /** Inject into GlobalActionsComponent.  */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 2f8d9b8..6e1a482 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -87,9 +87,6 @@
     val NOTIFICATION_GROUP_DISMISSAL_ANIMATION =
         releasedFlag(259217907, "notification_group_dismissal_animation")
 
-    // TODO(b/257506350): Tracking Bug
-    @JvmField val FSI_CHROME = unreleasedFlag(117, "fsi_chrome")
-
     @JvmField
     val SIMPLIFIED_APPEAR_FRACTION =
         unreleasedFlag(259395680, "simplified_appear_fraction", teamfood = true)
@@ -189,10 +186,6 @@
     @JvmField
     val REVAMPED_WALLPAPER_UI = releasedFlag(222, "revamped_wallpaper_ui")
 
-    /** A different path for unocclusion transitions back to keyguard */
-    // TODO(b/262859270): Tracking Bug
-    @JvmField val UNOCCLUSION_TRANSITION = releasedFlag(223, "unocclusion_transition")
-
     // flag for controlling auto pin confirmation and material u shapes in bouncer
     @JvmField
     val AUTO_PIN_CONFIRMATION =
@@ -382,6 +375,9 @@
     // TODO(b/265045965): Tracking Bug
     val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot")
 
+    @JvmField
+    val ENABLE_LOW_LIGHT_CLOCK_UNDOCKED = unreleasedFlag(1004, "enable_low_light_clock_undocked")
+
     // 1100 - windowing
     @Keep
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 02bee3e..2ad1ab7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -127,8 +127,6 @@
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -522,8 +520,6 @@
 
     private CentralSurfaces mCentralSurfaces;
 
-    private boolean mUnocclusionTransitionFlagEnabled = false;
-
     private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
             new DeviceConfig.OnPropertiesChangedListener() {
             @Override
@@ -970,9 +966,6 @@
                 public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
                         RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
                         IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
-                    if (!mUnocclusionTransitionFlagEnabled) {
-                        setOccluded(true /* isOccluded */, true /* animate */);
-                    }
                     if (apps == null || apps.length == 0 || apps[0] == null) {
                         if (DEBUG) {
                             Log.d(TAG, "No apps provided to the OccludeByDream runner; "
@@ -1023,7 +1016,7 @@
                             @Override
                             public void onAnimationEnd(Animator animation) {
                                 try {
-                                    if (!mIsCancelled && mUnocclusionTransitionFlagEnabled) {
+                                    if (!mIsCancelled) {
                                         // We're already on the main thread, don't queue this call
                                         handleSetOccluded(true /* isOccluded */,
                                                 false /* animate */);
@@ -1200,7 +1193,6 @@
             ScreenOnCoordinator screenOnCoordinator,
             InteractionJankMonitor interactionJankMonitor,
             DreamOverlayStateController dreamOverlayStateController,
-            FeatureFlags featureFlags,
             Lazy<ShadeController> shadeControllerLazy,
             Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
             Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
@@ -1259,7 +1251,6 @@
 
         mDreamOpenAnimationDuration = (int) DREAMING_ANIMATION_DURATION_MS;
         mDreamCloseAnimationDuration = (int) LOCKSCREEN_ANIMATION_DURATION_MS;
-        mUnocclusionTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION);
     }
 
     public void userActivity() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 98d3570..47ef0fa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -39,7 +39,6 @@
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -113,7 +112,6 @@
             ScreenOnCoordinator screenOnCoordinator,
             InteractionJankMonitor interactionJankMonitor,
             DreamOverlayStateController dreamOverlayStateController,
-            FeatureFlags featureFlags,
             Lazy<ShadeController> shadeController,
             Lazy<NotificationShadeWindowController> notificationShadeWindowController,
             Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
@@ -144,7 +142,6 @@
                 screenOnCoordinator,
                 interactionJankMonitor,
                 dreamOverlayStateController,
-                featureFlags,
                 shadeController,
                 notificationShadeWindowController,
                 activityLaunchAnimator,
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index be615d6..f7b7db4 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -22,15 +22,18 @@
 import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
+import android.os.Build
 import android.os.UserManager
 import android.util.Log
-import com.android.internal.logging.UiEvent
-import com.android.internal.logging.UiEventLogger
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
 import com.android.systemui.util.kotlin.getOrNull
+import com.android.wm.shell.bubbles.Bubble
 import com.android.wm.shell.bubbles.Bubbles
+import com.android.wm.shell.bubbles.Bubbles.BubbleExpandListener
 import java.util.Optional
+import java.util.concurrent.atomic.AtomicReference
 import javax.inject.Inject
 
 /**
@@ -41,18 +44,40 @@
  * Currently, we only support a single task per time.
  */
 @SysUISingleton
-internal class NoteTaskController
+class NoteTaskController
 @Inject
 constructor(
     private val context: Context,
     private val resolver: NoteTaskInfoResolver,
+    private val eventLogger: NoteTaskEventLogger,
     private val optionalBubbles: Optional<Bubbles>,
-    private val optionalKeyguardManager: Optional<KeyguardManager>,
     private val optionalUserManager: Optional<UserManager>,
+    private val optionalKeyguardManager: Optional<KeyguardManager>,
     @NoteTaskEnabledKey private val isEnabled: Boolean,
-    private val uiEventLogger: UiEventLogger,
 ) {
 
+    @VisibleForTesting val infoReference = AtomicReference<NoteTaskInfo?>()
+
+    /** @see BubbleExpandListener */
+    fun onBubbleExpandChanged(isExpanding: Boolean, key: String?) {
+        if (!isEnabled) return
+
+        if (key != Bubble.KEY_APP_BUBBLE) return
+
+        val info = infoReference.getAndSet(null)
+
+        // Safe guard mechanism, this callback should only be called for app bubbles.
+        if (info?.launchMode != NoteTaskLaunchMode.AppBubble) return
+
+        if (isExpanding) {
+            logDebug { "onBubbleExpandChanged - expanding: $info" }
+            eventLogger.logNoteTaskOpened(info)
+        } else {
+            logDebug { "onBubbleExpandChanged - collapsing: $info" }
+            eventLogger.logNoteTaskClosed(info)
+        }
+    }
+
     /**
      * Shows a note task. How the task is shown will depend on when the method is invoked.
      *
@@ -69,32 +94,50 @@
      * That will let users open other apps in full screen, and take contextual notes.
      */
     @JvmOverloads
-    fun showNoteTask(isInMultiWindowMode: Boolean = false, uiEvent: ShowNoteTaskUiEvent? = null) {
-
+    fun showNoteTask(
+        entryPoint: NoteTaskEntryPoint,
+        isInMultiWindowMode: Boolean = false,
+    ) {
         if (!isEnabled) return
 
         val bubbles = optionalBubbles.getOrNull() ?: return
-        val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
         val userManager = optionalUserManager.getOrNull() ?: return
+        val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
 
         // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
         if (!userManager.isUserUnlocked) return
 
-        val noteTaskInfo = resolver.resolveInfo() ?: return
+        val info =
+            resolver.resolveInfo(
+                entryPoint = entryPoint,
+                isInMultiWindowMode = isInMultiWindowMode,
+                isKeyguardLocked = keyguardManager.isKeyguardLocked,
+            )
+                ?: return
 
-        uiEvent?.let { uiEventLogger.log(it, noteTaskInfo.uid, noteTaskInfo.packageName) }
+        infoReference.set(info)
 
         // TODO(b/266686199): We should handle when app not available. For now, we log.
-        val intent = noteTaskInfo.toCreateNoteIntent()
+        val intent = createNoteIntent(info)
         try {
-            if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) {
-                context.startActivity(intent)
-            } else {
-                bubbles.showOrHideAppBubble(intent)
+            logDebug { "onShowNoteTask - start: $info" }
+            when (info.launchMode) {
+                is NoteTaskLaunchMode.AppBubble -> {
+                    bubbles.showOrHideAppBubble(intent)
+                    // App bubble logging happens on `onBubbleExpandChanged`.
+                    logDebug { "onShowNoteTask - opened as app bubble: $info" }
+                }
+                is NoteTaskLaunchMode.Activity -> {
+                    context.startActivity(intent)
+                    eventLogger.logNoteTaskOpened(info)
+                    logDebug { "onShowNoteTask - opened as activity: $info" }
+                }
             }
+            logDebug { "onShowNoteTask - success: $info" }
         } catch (e: ActivityNotFoundException) {
-            Log.e(TAG, "Activity not found for action: $ACTION_CREATE_NOTE.", e)
+            logDebug { "onShowNoteTask - failed: $info" }
         }
+        logDebug { "onShowNoteTask - compoleted: $info" }
     }
 
     /**
@@ -119,41 +162,12 @@
             enabledState,
             PackageManager.DONT_KILL_APP,
         )
-    }
 
-    /** IDs of UI events accepted by [showNoteTask]. */
-    enum class ShowNoteTaskUiEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
-        @UiEvent(doc = "User opened a note by tapping on the lockscreen shortcut.")
-        NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE(1294),
-
-        /* ktlint-disable max-line-length */
-        @UiEvent(
-            doc =
-                "User opened a note by pressing the stylus tail button while the screen was unlocked."
-        )
-        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON(1295),
-        @UiEvent(
-            doc =
-                "User opened a note by pressing the stylus tail button while the screen was locked."
-        )
-        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED(1296),
-        @UiEvent(doc = "User opened a note by tapping on an app shortcut.")
-        NOTE_OPENED_VIA_SHORTCUT(1297);
-
-        override fun getId() = _id
+        logDebug { "setNoteTaskShortcutEnabled - completed: $isEnabled" }
     }
 
     companion object {
-        private val TAG = NoteTaskController::class.simpleName.orEmpty()
-
-        private fun NoteTaskInfoResolver.NoteTaskInfo.toCreateNoteIntent(): Intent {
-            return Intent(ACTION_CREATE_NOTE)
-                .setPackage(packageName)
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint
-                // was used to start it.
-                .putExtra(INTENT_EXTRA_USE_STYLUS_MODE, true)
-        }
+        val TAG = NoteTaskController::class.simpleName.orEmpty()
 
         // TODO(b/254604589): Use final KeyEvent.KEYCODE_* instead.
         const val NOTE_TASK_KEY_EVENT = 311
@@ -165,3 +179,17 @@
         const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE"
     }
 }
+
+private fun createNoteIntent(info: NoteTaskInfo): Intent =
+    Intent(NoteTaskController.ACTION_CREATE_NOTE)
+        .setPackage(info.packageName)
+        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint
+        // was used to start it.
+        .putExtra(NoteTaskController.INTENT_EXTRA_USE_STYLUS_MODE, true)
+
+private inline fun logDebug(message: () -> String) {
+    if (Build.IS_DEBUGGABLE) {
+        Log.d(NoteTaskController.TAG, message())
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt
index e0bf1da..a256391 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt
@@ -19,4 +19,4 @@
 import javax.inject.Qualifier
 
 /** Key associated with a [Boolean] flag that enables or disables the note task feature. */
-@Qualifier internal annotation class NoteTaskEnabledKey
+@Qualifier annotation class NoteTaskEnabledKey
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
new file mode 100644
index 0000000..acc537a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.notetask
+
+import com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceConfig
+import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
+import com.android.systemui.screenshot.AppClipsTrampolineActivity
+
+/**
+ * Supported entry points for [NoteTaskController.showNoteTask].
+ *
+ * An entry point represents where the note task has ben called from. In rare cases, it may
+ * represent a "re-entry" (i.e., [APP_CLIPS]).
+ */
+enum class NoteTaskEntryPoint {
+
+    /** @see [LaunchNoteTaskActivity] */
+    WIDGET_PICKER_SHORTCUT,
+
+    /** @see [NoteTaskQuickAffordanceConfig] */
+    QUICK_AFFORDANCE,
+
+    /** @see [NoteTaskInitializer.callbacks] */
+    TAIL_BUTTON,
+
+    /** @see [AppClipsTrampolineActivity] */
+    APP_CLIPS,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
new file mode 100644
index 0000000..16dd16e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.notetask
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.notetask.NoteTaskEntryPoint.APP_CLIPS
+import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
+import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON
+import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+import javax.inject.Inject
+
+/**
+ * A wrapper around [UiEventLogger] specialized in the note taking UI events.
+ *
+ * if the accepted [NoteTaskInfo] contains a [NoteTaskInfo.entryPoint], it will be logged as the
+ * correct [NoteTaskUiEvent]. If null, it will be ignored.
+ *
+ * @see NoteTaskController for usage examples.
+ */
+class NoteTaskEventLogger @Inject constructor(private val uiEventLogger: UiEventLogger) {
+
+    /** Logs a [NoteTaskInfo] as an **open** [NoteTaskUiEvent], including package name and uid. */
+    fun logNoteTaskOpened(info: NoteTaskInfo) {
+        val event =
+            when (info.entryPoint) {
+                TAIL_BUTTON -> {
+                    if (info.isKeyguardLocked) {
+                        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+                    } else {
+                        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
+                    }
+                }
+                WIDGET_PICKER_SHORTCUT -> NOTE_OPENED_VIA_SHORTCUT
+                QUICK_AFFORDANCE -> NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
+                APP_CLIPS -> return
+                null -> return
+            }
+        uiEventLogger.log(event, info.uid, info.packageName)
+    }
+
+    /** Logs a [NoteTaskInfo] as a **closed** [NoteTaskUiEvent], including package name and uid. */
+    fun logNoteTaskClosed(info: NoteTaskInfo) {
+        val event =
+            when (info.entryPoint) {
+                TAIL_BUTTON -> {
+                    if (info.isKeyguardLocked) {
+                        NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+                    } else {
+                        NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON
+                    }
+                }
+                WIDGET_PICKER_SHORTCUT -> return
+                QUICK_AFFORDANCE -> return
+                APP_CLIPS -> return
+                null -> return
+            }
+        uiEventLogger.log(event, info.uid, info.packageName)
+    }
+
+    /** IDs of UI events accepted by [NoteTaskController]. */
+    enum class NoteTaskUiEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+
+        @UiEvent(doc = "User opened a note by tapping on the lockscreen shortcut.")
+        NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE(1294),
+
+        @UiEvent(doc = "User opened a note by pressing the stylus tail button while the screen was unlocked.") // ktlint-disable max-line-length
+        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON(1295),
+
+        @UiEvent(doc = "User opened a note by pressing the stylus tail button while the screen was locked.") // ktlint-disable max-line-length
+        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED(1296),
+
+        @UiEvent(doc = "User opened a note by tapping on an app shortcut.")
+        NOTE_OPENED_VIA_SHORTCUT(1297),
+
+        @UiEvent(doc = "Note closed via a tail button while device is unlocked")
+        NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON(1311),
+
+        @UiEvent(doc = "Note closed via a tail button while device is locked")
+        NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED(1312);
+
+        override fun getId() = _id
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt
new file mode 100644
index 0000000..28d7647
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.notetask
+
+/** Contextual information required to launch a Note Task by [NoteTaskController]. */
+data class NoteTaskInfo(
+    val packageName: String,
+    val uid: Int,
+    val entryPoint: NoteTaskEntryPoint? = null,
+    val isInMultiWindowMode: Boolean = false,
+    val isKeyguardLocked: Boolean = false,
+) {
+
+    val launchMode: NoteTaskLaunchMode =
+        if (isInMultiWindowMode || isKeyguardLocked) {
+            NoteTaskLaunchMode.Activity
+        } else {
+            NoteTaskLaunchMode.AppBubble
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
index bd822d4..b5d757c 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
@@ -19,51 +19,56 @@
 import android.app.role.RoleManager
 import android.content.Context
 import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ApplicationInfoFlags
 import android.os.UserHandle
 import android.util.Log
 import javax.inject.Inject
 
-internal class NoteTaskInfoResolver
+class NoteTaskInfoResolver
 @Inject
 constructor(
     private val context: Context,
     private val roleManager: RoleManager,
     private val packageManager: PackageManager,
 ) {
-    fun resolveInfo(): NoteTaskInfo? {
+    fun resolveInfo(
+        entryPoint: NoteTaskEntryPoint? = null,
+        isInMultiWindowMode: Boolean = false,
+        isKeyguardLocked: Boolean = false,
+    ): NoteTaskInfo? {
         // TODO(b/267634412): Select UserHandle depending on where the user initiated note-taking.
         val user = context.user
         val packageName = roleManager.getRoleHoldersAsUser(ROLE_NOTES, user).firstOrNull()
 
         if (packageName.isNullOrEmpty()) return null
 
-        return NoteTaskInfo(packageName, packageManager.getUidOf(packageName, user))
+        return NoteTaskInfo(
+            packageName = packageName,
+            uid = packageManager.getUidOf(packageName, user),
+            entryPoint = entryPoint,
+            isInMultiWindowMode = isInMultiWindowMode,
+            isKeyguardLocked = isKeyguardLocked,
+        )
     }
 
-    /** Package name and kernel user-ID of a note-taking app. */
-    data class NoteTaskInfo(val packageName: String, val uid: Int)
-
     companion object {
         private val TAG = NoteTaskInfoResolver::class.simpleName.orEmpty()
 
-        private val EMPTY_APPLICATION_INFO_FLAGS = PackageManager.ApplicationInfoFlags.of(0)!!
+        // TODO(b/265912743): Use RoleManager.NOTES_ROLE instead.
+        const val ROLE_NOTES = "android.app.role.NOTES"
+
+        private val EMPTY_APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0)!!
 
         /**
          * Returns the kernel user-ID of [packageName] for a [user]. Returns zero if the app cannot
          * be found.
          */
-        private fun PackageManager.getUidOf(packageName: String, user: UserHandle): Int {
-            val applicationInfo =
-                try {
-                    getApplicationInfoAsUser(packageName, EMPTY_APPLICATION_INFO_FLAGS, user)
-                } catch (e: PackageManager.NameNotFoundException) {
-                    Log.e(TAG, "Couldn't find notes app UID", e)
-                    return 0
-                }
-            return applicationInfo.uid
-        }
-
-        // TODO(b/265912743): Use RoleManager.NOTES_ROLE instead.
-        const val ROLE_NOTES = "android.app.role.NOTES"
+        private fun PackageManager.getUidOf(packageName: String, user: UserHandle): Int =
+            try {
+                getApplicationInfoAsUser(packageName, EMPTY_APPLICATION_INFO_FLAGS, user).uid
+            } catch (e: PackageManager.NameNotFoundException) {
+                Log.e(TAG, "Couldn't find notes app UID", e)
+                0
+            }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index d40bf2b..3f4f8d5 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -13,13 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.systemui.notetask
 
-import android.app.KeyguardManager
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.util.kotlin.getOrNull
 import com.android.wm.shell.bubbles.Bubbles
 import java.util.Optional
 import javax.inject.Inject
@@ -28,11 +25,10 @@
 internal class NoteTaskInitializer
 @Inject
 constructor(
-    private val optionalBubbles: Optional<Bubbles>,
-    private val noteTaskController: NoteTaskController,
+    private val controller: NoteTaskController,
     private val commandQueue: CommandQueue,
+    private val optionalBubbles: Optional<Bubbles>,
     @NoteTaskEnabledKey private val isEnabled: Boolean,
-    private val optionalKeyguardManager: Optional<KeyguardManager>,
 ) {
 
     @VisibleForTesting
@@ -40,29 +36,17 @@
         object : CommandQueue.Callbacks {
             override fun handleSystemKey(keyCode: Int) {
                 if (keyCode == NoteTaskController.NOTE_TASK_KEY_EVENT) {
-                    showNoteTask()
+                    controller.showNoteTask(NoteTaskEntryPoint.TAIL_BUTTON)
                 }
             }
         }
 
-    private fun showNoteTask() {
-        val uiEvent =
-            if (optionalKeyguardManager.isKeyguardLocked) {
-                NoteTaskController.ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
-            } else {
-                NoteTaskController.ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
-            }
-        noteTaskController.showNoteTask(uiEvent = uiEvent)
-    }
-
     fun initialize() {
-        if (isEnabled && optionalBubbles.isPresent) {
-            commandQueue.addCallback(callbacks)
-        }
-        noteTaskController.setNoteTaskShortcutEnabled(isEnabled)
+        controller.setNoteTaskShortcutEnabled(isEnabled)
+
+        // Guard against feature not being enabled or mandatory dependencies aren't available.
+        if (!isEnabled || optionalBubbles.isEmpty) return
+
+        commandQueue.addCallback(callbacks)
     }
 }
-
-private val Optional<KeyguardManager>.isKeyguardLocked: Boolean
-    // If there's no KeyguardManager, assume that the keyguard is not locked.
-    get() = getOrNull()?.isKeyguardLocked ?: false
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt
new file mode 100644
index 0000000..836e103f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.notetask
+
+import android.content.Context
+import com.android.wm.shell.bubbles.Bubbles
+
+/**
+ * Supported ways for launching a note taking experience.
+ *
+ * @see [NoteTaskController.showNoteTask]
+ */
+sealed class NoteTaskLaunchMode {
+
+    /** @see Bubbles.showOrHideAppBubble */
+    object AppBubble : NoteTaskLaunchMode()
+
+    /** @see Context.startActivity */
+    object Activity : NoteTaskLaunchMode()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index b8800a2..f16110d 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -36,7 +36,7 @@
 
 /** Compose all dependencies required by Note Task feature. */
 @Module(includes = [NoteTaskQuickAffordanceModule::class])
-internal interface NoteTaskModule {
+interface NoteTaskModule {
 
     @[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)]
     fun LaunchNoteTaskActivity.bindNoteTaskLauncherActivity(): Activity
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
index 43869cc..30660c4 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
@@ -27,12 +27,12 @@
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.PickerScreenState
 import com.android.systemui.notetask.NoteTaskController
-import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
 import com.android.systemui.notetask.NoteTaskEnabledKey
+import com.android.systemui.notetask.NoteTaskEntryPoint
 import javax.inject.Inject
 import kotlinx.coroutines.flow.flowOf
 
-internal class NoteTaskQuickAffordanceConfig
+class NoteTaskQuickAffordanceConfig
 @Inject
 constructor(
     context: Context,
@@ -66,7 +66,7 @@
 
     override fun onTriggered(expandable: Expandable?): OnTriggeredResult {
         noteTaskController.showNoteTask(
-            uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
+            entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE,
         )
         return OnTriggeredResult.Handled
     }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
index 7cb932a..2d63dbc 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
@@ -22,7 +22,7 @@
 import dagger.multibindings.IntoSet
 
 @Module
-internal interface NoteTaskQuickAffordanceModule {
+interface NoteTaskQuickAffordanceModule {
 
     @[Binds IntoSet]
     fun NoteTaskQuickAffordanceConfig.bindNoteTaskQuickAffordance(): KeyguardQuickAffordanceConfig
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
index 6ab0da6..0a1d008 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
@@ -36,7 +36,7 @@
  * href="https://developer.android.com/develop/ui/views/launch/shortcuts/creating-shortcuts#custom-pinned">Creating
  * a custom shortcut activity</a>
  */
-internal class CreateNoteTaskShortcutActivity @Inject constructor() : ComponentActivity() {
+class CreateNoteTaskShortcutActivity @Inject constructor() : ComponentActivity() {
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
index 3ac5bfa..2b84bf8 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
@@ -21,11 +21,11 @@
 import android.os.Bundle
 import androidx.activity.ComponentActivity
 import com.android.systemui.notetask.NoteTaskController
-import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
+import com.android.systemui.notetask.NoteTaskEntryPoint
 import javax.inject.Inject
 
 /** Activity responsible for launching the note experience, and finish. */
-internal class LaunchNoteTaskActivity
+class LaunchNoteTaskActivity
 @Inject
 constructor(
     private val noteTaskController: NoteTaskController,
@@ -35,8 +35,8 @@
         super.onCreate(savedInstanceState)
 
         noteTaskController.showNoteTask(
+            entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT,
             isInMultiWindowMode = isInMultiWindowMode,
-            uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT,
         )
 
         finish()
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
index 1946b8e..eda38e4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
@@ -50,6 +50,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.notetask.NoteTaskController;
+import com.android.systemui.notetask.NoteTaskEntryPoint;
 import com.android.systemui.settings.UserTracker;
 import com.android.wm.shell.bubbles.Bubbles;
 
@@ -239,9 +240,8 @@
             // Broadcast no longer required, setting it to null.
             mKillAppClipsBroadcastIntent = null;
 
-            // Expand the note bubble before returning the result. As App Clips API is only
-            // available when in a bubble, isInMultiWindowMode is always false below.
-            mNoteTaskController.showNoteTask(false);
+            // Expand the note bubble before returning the result.
+            mNoteTaskController.showNoteTask(NoteTaskEntryPoint.APP_CLIPS);
             setResult(RESULT_OK, convertedData);
             finish();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index b502b4d..95206e5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -465,7 +465,7 @@
     private int mPanelAlpha;
     private Runnable mPanelAlphaEndAction;
     private float mBottomAreaShadeAlpha;
-    private final ValueAnimator mBottomAreaShadeAlphaAnimator;
+    final ValueAnimator mBottomAreaShadeAlphaAnimator;
     private final AnimatableProperty mPanelAlphaAnimator = AnimatableProperty.from("panelAlpha",
             NotificationPanelView::setPanelAlphaInternal,
             NotificationPanelView::getCurrentPanelAlpha,
@@ -597,7 +597,6 @@
     private int mLockscreenToDreamingTransitionTranslationY;
     private int mGoneToDreamingTransitionTranslationY;
     private int mLockscreenToOccludedTransitionTranslationY;
-    private boolean mUnocclusionTransitionFlagEnabled = false;
 
     private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */,
             mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */);
@@ -886,7 +885,6 @@
         mNotificationPanelUnfoldAnimationController = unfoldComponent.map(
                 SysUIUnfoldComponent::getNotificationPanelUnfoldAnimationController);
 
-        mUnocclusionTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION);
         updateUserSwitcherFlags();
         mKeyguardBottomAreaViewModel = keyguardBottomAreaViewModel;
         mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor;
@@ -1045,62 +1043,50 @@
         mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
                 controller.setup(mNotificationContainerParent));
 
-        if (mUnocclusionTransitionFlagEnabled) {
-            // Dreaming->Lockscreen
-            collectFlow(mView, mKeyguardTransitionInteractor.getDreamingToLockscreenTransition(),
-                    mDreamingToLockscreenTransition, mMainDispatcher);
-            collectFlow(mView, mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha(),
-                    setTransitionAlpha(mNotificationStackScrollLayoutController),
-                    mMainDispatcher);
-            collectFlow(mView, mDreamingToLockscreenTransitionViewModel.lockscreenTranslationY(
-                    mDreamingToLockscreenTransitionTranslationY),
-                    setTransitionY(mNotificationStackScrollLayoutController),
-                    mMainDispatcher);
+        // Dreaming->Lockscreen
+        collectFlow(mView, mKeyguardTransitionInteractor.getDreamingToLockscreenTransition(),
+                mDreamingToLockscreenTransition, mMainDispatcher);
+        collectFlow(mView, mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha(),
+                setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
+        collectFlow(mView, mDreamingToLockscreenTransitionViewModel.lockscreenTranslationY(
+                mDreamingToLockscreenTransitionTranslationY),
+                setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
 
-            // Occluded->Lockscreen
-            collectFlow(mView, mKeyguardTransitionInteractor.getOccludedToLockscreenTransition(),
-                    mOccludedToLockscreenTransition, mMainDispatcher);
-            collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha(),
-                    setTransitionAlpha(mNotificationStackScrollLayoutController),
-                    mMainDispatcher);
-            collectFlow(mView, mOccludedToLockscreenTransitionViewModel.lockscreenTranslationY(
-                    mOccludedToLockscreenTransitionTranslationY),
-                    setTransitionY(mNotificationStackScrollLayoutController),
-                    mMainDispatcher);
+        // Occluded->Lockscreen
+        collectFlow(mView, mKeyguardTransitionInteractor.getOccludedToLockscreenTransition(),
+                mOccludedToLockscreenTransition, mMainDispatcher);
+        collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha(),
+                setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
+        collectFlow(mView, mOccludedToLockscreenTransitionViewModel.lockscreenTranslationY(
+                mOccludedToLockscreenTransitionTranslationY),
+                setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
 
-            // Lockscreen->Dreaming
-            collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToDreamingTransition(),
-                    mLockscreenToDreamingTransition, mMainDispatcher);
-            collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(),
-                    setTransitionAlpha(mNotificationStackScrollLayoutController),
-                    mMainDispatcher);
-            collectFlow(mView, mLockscreenToDreamingTransitionViewModel.lockscreenTranslationY(
-                    mLockscreenToDreamingTransitionTranslationY),
-                    setTransitionY(mNotificationStackScrollLayoutController),
-                    mMainDispatcher);
+        // Lockscreen->Dreaming
+        collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToDreamingTransition(),
+                mLockscreenToDreamingTransition, mMainDispatcher);
+        collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(),
+                setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
+        collectFlow(mView, mLockscreenToDreamingTransitionViewModel.lockscreenTranslationY(
+                mLockscreenToDreamingTransitionTranslationY),
+                setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
 
-            // Gone->Dreaming
-            collectFlow(mView, mKeyguardTransitionInteractor.getGoneToDreamingTransition(),
-                    mGoneToDreamingTransition, mMainDispatcher);
-            collectFlow(mView, mGoneToDreamingTransitionViewModel.getLockscreenAlpha(),
-                    setTransitionAlpha(mNotificationStackScrollLayoutController),
-                    mMainDispatcher);
-            collectFlow(mView, mGoneToDreamingTransitionViewModel.lockscreenTranslationY(
-                    mGoneToDreamingTransitionTranslationY),
-                    setTransitionY(mNotificationStackScrollLayoutController),
-                    mMainDispatcher);
+        // Gone->Dreaming
+        collectFlow(mView, mKeyguardTransitionInteractor.getGoneToDreamingTransition(),
+                mGoneToDreamingTransition, mMainDispatcher);
+        collectFlow(mView, mGoneToDreamingTransitionViewModel.getLockscreenAlpha(),
+                setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
+        collectFlow(mView, mGoneToDreamingTransitionViewModel.lockscreenTranslationY(
+                mGoneToDreamingTransitionTranslationY),
+                setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
 
-            // Lockscreen->Occluded
-            collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToOccludedTransition(),
-                    mLockscreenToOccludedTransition, mMainDispatcher);
-            collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha(),
-                    setTransitionAlpha(mNotificationStackScrollLayoutController),
-                    mMainDispatcher);
-            collectFlow(mView, mLockscreenToOccludedTransitionViewModel.lockscreenTranslationY(
-                    mLockscreenToOccludedTransitionTranslationY),
-                    setTransitionY(mNotificationStackScrollLayoutController),
-                    mMainDispatcher);
-        }
+        // Lockscreen->Occluded
+        collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToOccludedTransition(),
+                mLockscreenToOccludedTransition, mMainDispatcher);
+        collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha(),
+                setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
+        collectFlow(mView, mLockscreenToOccludedTransitionViewModel.lockscreenTranslationY(
+                mLockscreenToOccludedTransitionTranslationY),
+                setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
     }
 
     @VisibleForTesting
@@ -2480,9 +2466,6 @@
     }
 
     private void onExpandingFinished() {
-        if (!mUnocclusionTransitionFlagEnabled) {
-            mScrimController.onExpandingFinished();
-        }
         mNotificationStackScrollLayoutController.onExpansionStopped();
         mHeadsUpManager.onExpandingFinished();
         mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed());
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 60fa865..87350b46 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -38,8 +38,6 @@
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dock.DockManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -132,7 +130,6 @@
             NotificationInsetsController notificationInsetsController,
             AmbientState ambientState,
             PulsingGestureListener pulsingGestureListener,
-            FeatureFlags featureFlags,
             KeyguardBouncerViewModel keyguardBouncerViewModel,
             KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory,
             AlternateBouncerInteractor alternateBouncerInteractor,
@@ -165,10 +162,8 @@
                 keyguardBouncerViewModel,
                 keyguardBouncerComponentFactory);
 
-        if (featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION)) {
-            collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
-                    mLockscreenToDreamingTransition);
-        }
+        collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
+                mLockscreenToDreamingTransition);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeRepo.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeRepo.kt
deleted file mode 100644
index b483228..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeRepo.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.android.systemui.statusbar.notification.fsi
-
-import android.app.PendingIntent
-import android.content.Context
-import android.content.pm.PackageManager
-import android.graphics.drawable.Drawable
-import android.os.RemoteException
-import android.service.dreams.IDreamManager
-import com.android.systemui.CoreStartable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider
-import com.android.systemui.statusbar.notification.fsi.FsiDebug.Companion.log
-import com.android.systemui.statusbar.phone.CentralSurfaces
-import java.util.concurrent.Executor
-import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-
-/**
- * Class that bridges the gap between clean app architecture and existing code. Provides new
- * implementation of StatusBarNotificationActivityStarter launchFullscreenIntent that pipes
- * one-directional data => FsiChromeViewModel => FsiChromeView.
- */
-@SysUISingleton
-class FsiChromeRepo
-@Inject
-constructor(
-    private val context: Context,
-    private val pm: PackageManager,
-    private val keyguardRepo: KeyguardRepository,
-    private val launchFullScreenIntentProvider: LaunchFullScreenIntentProvider,
-    private val featureFlags: FeatureFlags,
-    private val uiBgExecutor: Executor,
-    private val dreamManager: IDreamManager,
-    private val centralSurfaces: CentralSurfaces
-) : CoreStartable {
-
-    companion object {
-        private const val classTag = "FsiChromeRepo"
-    }
-
-    data class FSIInfo(
-        val appName: String,
-        val appIcon: Drawable,
-        val fullscreenIntent: PendingIntent
-    )
-
-    private val _infoFlow = MutableStateFlow<FSIInfo?>(null)
-    val infoFlow: StateFlow<FSIInfo?> = _infoFlow
-
-    override fun start() {
-        log("$classTag start listening for FSI notifications")
-
-        // Listen for FSI launch events for the lifetime of SystemUI.
-        launchFullScreenIntentProvider.registerListener { entry -> launchFullscreenIntent(entry) }
-    }
-
-    fun dismiss() {
-        _infoFlow.value = null
-    }
-
-    fun onFullscreen() {
-        // TODO(b/243421660) implement transition from container to fullscreen
-    }
-
-    fun stopScreenSaver() {
-        uiBgExecutor.execute {
-            try {
-                dreamManager.awaken()
-            } catch (e: RemoteException) {
-                e.printStackTrace()
-            }
-        }
-    }
-
-    fun launchFullscreenIntent(entry: NotificationEntry) {
-        if (!featureFlags.isEnabled(Flags.FSI_CHROME)) {
-            return
-        }
-        if (!keyguardRepo.isKeyguardShowing()) {
-            return
-        }
-        stopScreenSaver()
-
-        var appName = pm.getApplicationLabel(context.applicationInfo) as String
-        val appIcon = pm.getApplicationIcon(context.packageName)
-        val fullscreenIntent = entry.sbn.notification.fullScreenIntent
-
-        log("FsiChromeRepo launchFullscreenIntent appName=$appName appIcon $appIcon")
-        _infoFlow.value = FSIInfo(appName, appIcon, fullscreenIntent)
-
-        // If screen is off or we're showing AOD, show lockscreen.
-        centralSurfaces.wakeUpForFullScreenIntent()
-
-        // Don't show HUN since we're already showing FSI.
-        entry.notifyFullScreenIntentLaunched()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeView.kt
deleted file mode 100644
index 6e5fcf4..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeView.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-package com.android.systemui.statusbar.notification.fsi
-
-import android.content.Context
-import android.graphics.Color
-import android.graphics.Color.DKGRAY
-import android.graphics.Outline
-import android.util.AttributeSet
-import android.view.View
-import android.view.ViewOutlineProvider
-import android.widget.Button
-import android.widget.ImageView
-import android.widget.LinearLayout
-import android.widget.TextView
-import com.android.systemui.R
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.fsi.FsiDebug.Companion.log
-
-@SysUISingleton
-class FsiChromeView
-@JvmOverloads
-constructor(
-    context: Context?,
-    attrs: AttributeSet? = null,
-    defStyleAttr: Int = 0,
-    defStyleRes: Int = 0
-) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
-
-    companion object {
-        private const val classTag = "FsiChromeView"
-    }
-
-    lateinit var chromeContainer: LinearLayout
-    lateinit var appIconImageView: ImageView
-    lateinit var appNameTextView: TextView
-    lateinit var dismissButton: Button
-    lateinit var fullscreenButton: Button
-
-    private val cornerRadius: Float =
-        resources.getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat()
-    private val vertPadding: Int =
-        resources.getDimensionPixelSize(R.dimen.fsi_chrome_vertical_padding)
-    private val sidePadding: Int =
-        resources.getDimensionPixelSize(R.dimen.notification_side_paddings)
-
-    init {
-        log("$classTag init")
-    }
-
-    override fun onFinishInflate() {
-        log("$classTag onFinishInflate")
-        super.onFinishInflate()
-
-        setBackgroundColor(Color.TRANSPARENT)
-        setPadding(
-            sidePadding,
-            vertPadding,
-            sidePadding,
-            vertPadding
-        ) // Make smaller than fullscreen.
-
-        chromeContainer = findViewById(R.id.fsi_chrome)
-        chromeContainer.setBackgroundColor(DKGRAY)
-
-        appIconImageView = findViewById(R.id.fsi_app_icon)
-        appNameTextView = findViewById(R.id.fsi_app_name)
-        dismissButton = findViewById(R.id.fsi_dismiss_button)
-        fullscreenButton = findViewById(R.id.fsi_fullscreen_button)
-
-        outlineProvider =
-            object : ViewOutlineProvider() {
-                override fun getOutline(view: View, outline: Outline) {
-                    outline.setRoundRect(
-                        /* left */ sidePadding,
-                        /* top */ vertPadding,
-                        /* right */ view.width - sidePadding,
-                        /* bottom */ view.height - vertPadding,
-                        cornerRadius
-                    )
-                }
-            }
-        clipToOutline = true
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewBinder.kt
deleted file mode 100644
index 1a3927b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewBinder.kt
+++ /dev/null
@@ -1,99 +0,0 @@
-package com.android.systemui.statusbar.notification.fsi
-
-import android.content.Context
-import android.view.LayoutInflater
-import android.view.WindowManager
-import com.android.systemui.CoreStartable
-import com.android.systemui.R
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.statusbar.notification.fsi.FsiDebug.Companion.log
-import com.android.systemui.statusbar.phone.CentralSurfaces
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-import java.util.concurrent.Executor
-import javax.inject.Inject
-
-@SysUISingleton
-class FsiChromeViewBinder
-@Inject
-constructor(
-    val context: Context,
-    val windowManager: WindowManager,
-    val viewModelFactory: FsiChromeViewModelFactory,
-    val layoutInflater: LayoutInflater,
-    val centralSurfaces: CentralSurfaces,
-    @Main val mainExecutor: Executor,
-    @Application val scope: CoroutineScope,
-) : CoreStartable {
-
-    companion object {
-        private const val classTag = "FsiChromeViewBinder"
-    }
-
-    private val fsiChromeView =
-        layoutInflater.inflate(R.layout.fsi_chrome_view, null /* root */, false /* attachToRoot */)
-            as FsiChromeView
-
-    var addedToWindowManager = false
-    var cornerRadius: Int = context.resources.getDimensionPixelSize(
-            R.dimen.notification_corner_radius)
-
-    override fun start() {
-        val methodTag = "start"
-        log("$classTag $methodTag ")
-
-        scope.launch {
-            log("$classTag $methodTag launch ")
-            viewModelFactory.viewModelFlow.collect { vm -> updateForViewModel(vm) }
-        }
-    }
-
-    private fun updateForViewModel(vm: FsiChromeViewModel?) {
-        val methodTag = "updateForViewModel"
-
-        if (vm == null) {
-            log("$classTag $methodTag viewModel is null, removing from window manager")
-
-            if (addedToWindowManager) {
-                windowManager.removeView(fsiChromeView)
-                addedToWindowManager = false
-            }
-            return
-        }
-
-        bindViewModel(vm, windowManager)
-
-        if (addedToWindowManager) {
-            log("$classTag $methodTag already addedToWindowManager")
-        } else {
-            windowManager.addView(fsiChromeView, FsiTaskViewConfig.getWmLayoutParams("PackageName"))
-            addedToWindowManager = true
-        }
-    }
-
-    private fun bindViewModel(
-        vm: FsiChromeViewModel,
-        windowManager: WindowManager,
-    ) {
-        log("$classTag bindViewModel")
-
-        fsiChromeView.appIconImageView.setImageDrawable(vm.appIcon)
-        fsiChromeView.appNameTextView.text = vm.appName
-
-        fsiChromeView.dismissButton.setOnClickListener { vm.onDismiss() }
-        fsiChromeView.fullscreenButton.setOnClickListener { vm.onFullscreen() }
-
-        vm.taskView.cornerRadius = cornerRadius.toFloat()
-        vm.taskView.startActivity(
-            vm.fsi,
-            FsiTaskViewConfig.getFillInIntent(),
-            FsiTaskViewConfig.getActivityOptions(context, windowManager),
-            FsiTaskViewConfig.getLaunchBounds(windowManager)
-        )
-
-        log("$classTag bindViewModel started taskview activity")
-        fsiChromeView.addView(vm.taskView)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactory.kt
deleted file mode 100644
index 1ca698b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactory.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-package com.android.systemui.statusbar.notification.fsi
-
-import android.annotation.UiContext
-import android.app.PendingIntent
-import android.content.Context
-import android.graphics.drawable.Drawable
-import com.android.systemui.CoreStartable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.statusbar.notification.fsi.FsiDebug.Companion.log
-import com.android.wm.shell.TaskView
-import com.android.wm.shell.TaskViewFactory
-import java.util.Optional
-import java.util.concurrent.Executor
-import javax.inject.Inject
-import kotlin.coroutines.resume
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.suspendCancellableCoroutine
-
-/**
- * Handle view-related data for fullscreen intent container on lockscreen. Wraps FsiChromeRepo,
- * transforms events/state into view-relevant representation for FsiChromeView. Alive for lifetime
- * of SystemUI.
- */
-@SysUISingleton
-class FsiChromeViewModelFactory
-@Inject
-constructor(
-    val repo: FsiChromeRepo,
-    val taskViewFactory: Optional<TaskViewFactory>,
-    @UiContext val context: Context,
-    @Main val mainExecutor: Executor,
-) : CoreStartable {
-
-    companion object {
-        private const val classTag = "FsiChromeViewModelFactory"
-    }
-
-    val viewModelFlow: Flow<FsiChromeViewModel?> =
-        repo.infoFlow.mapLatest { fsiInfo ->
-            fsiInfo?.let {
-                log("$classTag viewModelFlow got new fsiInfo")
-
-                // mapLatest emits null when FSIInfo is null
-                FsiChromeViewModel(
-                    fsiInfo.appName,
-                    fsiInfo.appIcon,
-                    createTaskView(),
-                    fsiInfo.fullscreenIntent,
-                    repo
-                )
-            }
-        }
-
-    override fun start() {
-        log("$classTag start")
-    }
-
-    private suspend fun createTaskView(): TaskView = suspendCancellableCoroutine { k ->
-        log("$classTag createTaskView")
-
-        taskViewFactory.get().create(context, mainExecutor) { taskView -> k.resume(taskView) }
-    }
-}
-
-// Alive for lifetime of FSI.
-data class FsiChromeViewModel(
-    val appName: String,
-    val appIcon: Drawable,
-    val taskView: TaskView,
-    val fsi: PendingIntent,
-    val repo: FsiChromeRepo
-) {
-    companion object {
-        private const val classTag = "FsiChromeViewModel"
-    }
-
-    fun onDismiss() {
-        log("$classTag onDismiss")
-        repo.dismiss()
-    }
-    fun onFullscreen() {
-        log("$classTag onFullscreen")
-        repo.onFullscreen()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiDebug.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiDebug.kt
deleted file mode 100644
index d9e3f8f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiDebug.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.android.systemui.statusbar.notification.fsi
-
-class FsiDebug {
-
-    companion object {
-        private const val debugTag = "FsiDebug"
-        private const val debug = true
-
-        fun log(s: Any) {
-            if (!debug) {
-                return
-            }
-            android.util.Log.d(debugTag, "$s")
-        }
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiTaskViewConfig.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiTaskViewConfig.kt
deleted file mode 100644
index 034ab56..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiTaskViewConfig.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-package com.android.systemui.statusbar.notification.fsi
-
-import android.app.ActivityOptions
-import android.content.Context
-import android.content.Intent
-import android.graphics.PixelFormat
-import android.graphics.Rect
-import android.os.Binder
-import android.view.ViewGroup
-import android.view.WindowManager
-
-/**
- * Config for adding the FsiChromeView window to WindowManager and starting the FSI activity.
- */
-class FsiTaskViewConfig {
-
-    companion object {
-
-        private const val classTag = "FsiTaskViewConfig"
-
-        fun getWmLayoutParams(packageName: String): WindowManager.LayoutParams {
-            val params: WindowManager.LayoutParams?
-            params =
-                WindowManager.LayoutParams(
-                    ViewGroup.LayoutParams.MATCH_PARENT,
-                    ViewGroup.LayoutParams.MATCH_PARENT,
-                    WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
-                    WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or
-                        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED or
-                            WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER,
-                    PixelFormat.TRANSLUCENT
-                )
-            params.setTrustedOverlay()
-            params.fitInsetsTypes = 0
-            params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
-            params.token = Binder()
-            params.packageName = packageName
-            params.layoutInDisplayCutoutMode =
-                WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
-            params.privateFlags =
-                params.privateFlags or WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
-            return params
-        }
-
-        fun getFillInIntent(): Intent {
-            val fillInIntent = Intent()
-            fillInIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
-            fillInIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
-            // FLAG_ACTIVITY_NEW_TASK is auto-applied because
-            // we're starting the FSI activity from a non-Activity context
-            return fillInIntent
-        }
-
-        fun getLaunchBounds(windowManager: WindowManager): Rect {
-            // TODO(b/243421660) check this works for non-resizeable activity
-            return Rect()
-        }
-
-        fun getActivityOptions(context: Context, windowManager: WindowManager): ActivityOptions {
-            // Custom options so there is no activity transition animation
-            val options =
-                ActivityOptions.makeCustomAnimation(context, 0 /* enterResId */, 0 /* exitResId */)
-
-            options.taskAlwaysOnTop = true
-
-            options.pendingIntentLaunchFlags =
-                Intent.FLAG_ACTIVITY_NEW_DOCUMENT or
-                    Intent.FLAG_ACTIVITY_MULTIPLE_TASK or
-                    Intent.FLAG_ACTIVITY_NEW_TASK
-
-            options.launchBounds = getLaunchBounds(windowManager)
-            return options
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 39e4000..4522e41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -443,6 +443,7 @@
         CancellationSignal cancellationSignal = new CancellationSignal();
         cancellationSignal.setOnCancelListener(
                 () -> runningInflations.values().forEach(CancellationSignal::cancel));
+
         return cancellationSignal;
     }
 
@@ -783,6 +784,7 @@
     public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress>
             implements InflationCallback, InflationTask {
 
+        private static final long IMG_PRELOAD_TIMEOUT_MS = 1000L;
         private final NotificationEntry mEntry;
         private final Context mContext;
         private final boolean mInflateSynchronously;
@@ -876,7 +878,7 @@
                         recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
                         mUsesIncreasedHeadsUpHeight, packageContext);
                 InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState();
-                return inflateSmartReplyViews(
+                InflationProgress result = inflateSmartReplyViews(
                         inflationProgress,
                         mReInflateFlags,
                         mEntry,
@@ -884,6 +886,11 @@
                         packageContext,
                         previousSmartReplyState,
                         mSmartRepliesInflater);
+
+                // wait for image resolver to finish preloading
+                mRow.getImageResolver().waitForPreloadedImages(IMG_PRELOAD_TIMEOUT_MS);
+
+                return result;
             } catch (Exception e) {
                 mError = e;
                 return null;
@@ -918,6 +925,9 @@
                 mCallback.handleInflationException(mRow.getEntry(),
                         new InflationException("Couldn't inflate contentViews" + e));
             }
+
+            // Cancel any image loading tasks, not useful any more
+            mRow.getImageResolver().cancelRunningTasks();
         }
 
         @Override
@@ -944,6 +954,9 @@
             // Notify the resolver that the inflation task has finished,
             // try to purge unnecessary cached entries.
             mRow.getImageResolver().purgeCache();
+
+            // Cancel any image loading tasks that have not completed at this point
+            mRow.getImageResolver().cancelRunningTasks();
         }
 
         private static class RtlEnabledContext extends ContextWrapper {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java
index 41eeada0..fe0b312 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java
@@ -22,8 +22,11 @@
 import android.util.Log;
 
 import java.util.Set;
+import java.util.concurrent.CancellationException;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * A cache for inline images of image messages.
@@ -56,12 +59,13 @@
     }
 
     @Override
-    public Drawable get(Uri uri) {
+    public Drawable get(Uri uri, long timeoutMs) {
         Drawable result = null;
         try {
-            result = mCache.get(uri).get();
-        } catch (InterruptedException | ExecutionException ex) {
-            Log.d(TAG, "get: Failed get image from " + uri);
+            result = mCache.get(uri).get(timeoutMs, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException | ExecutionException
+                | TimeoutException | CancellationException ex) {
+            Log.d(TAG, "get: Failed get image from " + uri + " " + ex);
         }
         return result;
     }
@@ -72,6 +76,15 @@
         mCache.entrySet().removeIf(entry -> !wantedSet.contains(entry.getKey()));
     }
 
+    @Override
+    public void cancelRunningTasks() {
+        mCache.forEach((key, value) -> {
+            if (value.getStatus() != AsyncTask.Status.FINISHED) {
+                value.cancel(true);
+            }
+        });
+    }
+
     private static class PreloadImageTask extends AsyncTask<Uri, Void, Drawable> {
         private final NotificationInlineImageResolver mResolver;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
index b05e64ab..c620f44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
@@ -23,6 +23,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcelable;
+import android.os.SystemClock;
 import android.util.Log;
 
 import com.android.internal.R;
@@ -45,6 +46,9 @@
 public class NotificationInlineImageResolver implements ImageResolver {
     private static final String TAG = NotificationInlineImageResolver.class.getSimpleName();
 
+    // Timeout for loading images from ImageCache when calling from UI thread
+    private static final long MAX_UI_THREAD_TIMEOUT_MS = 100L;
+
     private final Context mContext;
     private final ImageCache mImageCache;
     private Set<Uri> mWantedUriSet;
@@ -123,17 +127,25 @@
         return null;
     }
 
+    /**
+     * Loads an image from the Uri.
+     * This method is synchronous and is usually called from the Main thread.
+     * It will time-out after MAX_UI_THREAD_TIMEOUT_MS.
+     *
+     * @param uri Uri of the target image.
+     * @return drawable of the image, null if loading failed/timeout
+     */
     @Override
     public Drawable loadImage(Uri uri) {
-        return hasCache() ? loadImageFromCache(uri) : resolveImage(uri);
+        return hasCache() ? loadImageFromCache(uri, MAX_UI_THREAD_TIMEOUT_MS) : resolveImage(uri);
     }
 
-    private Drawable loadImageFromCache(Uri uri) {
+    private Drawable loadImageFromCache(Uri uri, long timeoutMs) {
         // if the uri isn't currently cached, try caching it first
         if (!mImageCache.hasEntry(uri)) {
             mImageCache.preload((uri));
         }
-        return mImageCache.get(uri);
+        return mImageCache.get(uri, timeoutMs);
     }
 
     /**
@@ -208,6 +220,30 @@
     }
 
     /**
+     * Wait for a maximum timeout for images to finish preloading
+     * @param timeoutMs total timeout time
+     */
+    void waitForPreloadedImages(long timeoutMs) {
+        if (!hasCache()) {
+            return;
+        }
+        Set<Uri> preloadedUris = getWantedUriSet();
+        if (preloadedUris != null) {
+            // Decrement remaining timeout after each image check
+            long endTimeMs = SystemClock.elapsedRealtime() + timeoutMs;
+            preloadedUris.forEach(
+                    uri -> loadImageFromCache(uri, endTimeMs - SystemClock.elapsedRealtime()));
+        }
+    }
+
+    void cancelRunningTasks() {
+        if (!hasCache()) {
+            return;
+        }
+        mImageCache.cancelRunningTasks();
+    }
+
+    /**
      * A interface for internal cache implementation of this resolver.
      */
     interface ImageCache {
@@ -216,7 +252,7 @@
          * @param uri The uri of the image.
          * @return Drawable of the image.
          */
-        Drawable get(Uri uri);
+        Drawable get(Uri uri, long timeoutMs);
 
         /**
          * Set the image resolver that actually resolves image from specified uri.
@@ -241,6 +277,11 @@
          * Purge unnecessary entries in the cache.
          */
         void purge();
+
+        /**
+         * Cancel all unfinished image loading tasks
+         */
+        void cancelRunningTasks();
     }
 
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 8dcfec7..9e62817 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -375,8 +375,6 @@
     void fadeKeyguardAfterLaunchTransition(Runnable beforeFading,
             Runnable endRunnable, Runnable cancelRunnable);
 
-    void animateKeyguardUnoccluding();
-
     void startLaunchTransitionTimeout();
 
     boolean hideKeyguardImpl(boolean forceStateChange);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index bfeca02..ae97407 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -164,6 +164,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
+import com.android.systemui.notetask.NoteTaskController;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.OverlayPlugin;
@@ -642,7 +643,7 @@
     private NotificationActivityStarter mNotificationActivityStarter;
     private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
     private final Optional<Bubbles> mBubblesOptional;
-    private final Bubbles.BubbleExpandListener mBubbleExpandListener;
+    private final Lazy<NoteTaskController> mNoteTaskControllerLazy;
     private final Optional<StartingSurface> mStartingSurfaceOptional;
 
     private final ActivityIntentHelper mActivityIntentHelper;
@@ -705,6 +706,7 @@
             WakefulnessLifecycle wakefulnessLifecycle,
             SysuiStatusBarStateController statusBarStateController,
             Optional<Bubbles> bubblesOptional,
+            Lazy<NoteTaskController> noteTaskControllerLazy,
             DeviceProvisionedController deviceProvisionedController,
             NavigationBarController navigationBarController,
             AccessibilityFloatingMenuController accessibilityFloatingMenuController,
@@ -795,6 +797,7 @@
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mStatusBarStateController = statusBarStateController;
         mBubblesOptional = bubblesOptional;
+        mNoteTaskControllerLazy = noteTaskControllerLazy;
         mDeviceProvisionedController = deviceProvisionedController;
         mNavigationBarController = navigationBarController;
         mAccessibilityFloatingMenuController = accessibilityFloatingMenuController;
@@ -852,9 +855,6 @@
         mShadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged);
         mShadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
 
-        mBubbleExpandListener = (isExpanding, key) ->
-                mContext.getMainExecutor().execute(this::updateScrimController);
-
         mActivityIntentHelper = new ActivityIntentHelper(mContext);
         mActivityLaunchAnimator = activityLaunchAnimator;
 
@@ -884,14 +884,21 @@
         }
     }
 
+    private void initBubbles(Bubbles bubbles) {
+        final Bubbles.BubbleExpandListener listener = (isExpanding, key) ->
+                mContext.getMainExecutor().execute(() -> {
+                    updateScrimController();
+                    mNoteTaskControllerLazy.get().onBubbleExpandChanged(isExpanding, key);
+                });
+        bubbles.setExpandListener(listener);
+    }
+
     @Override
     public void start() {
         mScreenLifecycle.addObserver(mScreenObserver);
         mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
         mUiModeManager = mContext.getSystemService(UiModeManager.class);
-        if (mBubblesOptional.isPresent()) {
-            mBubblesOptional.get().setExpandListener(mBubbleExpandListener);
-        }
+        mBubblesOptional.ifPresent(this::initBubbles);
 
         // Do not restart System UI when the bugreport flag changes.
         mFeatureFlags.addListener(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT, event -> {
@@ -2991,16 +2998,6 @@
     }
 
     /**
-     * Plays the animation when an activity that was occluding Keyguard goes away.
-     */
-    @Override
-    public void animateKeyguardUnoccluding() {
-        mNotificationPanelViewController.setExpandedFraction(0f);
-        mCommandQueueCallbacks.animateExpandNotificationsPanel();
-        mScrimController.setUnocclusionAnimationRunning(true);
-    }
-
-    /**
      * Starts the timeout when we try to start the affordances on Keyguard. We usually rely that
      * Keyguard goes away via fadeKeyguardAfterLaunchTransition, however, that might not happen
      * because the launched app crashed or something else went wrong.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 80093a3..8e0ec284 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -138,26 +138,12 @@
     private boolean mTransitioningToFullShade;
 
     /**
-     * Is there currently an unocclusion animation running. Used to avoid bright flickers
-     * of the notification scrim.
-     */
-    private boolean mUnOcclusionAnimationRunning;
-
-    /**
      * The percentage of the bouncer which is hidden. If 1, the bouncer is completely hidden. If
      * 0, the bouncer is visible.
      */
     @FloatRange(from = 0, to = 1)
     private float mBouncerHiddenFraction = KeyguardBouncerConstants.EXPANSION_HIDDEN;
 
-    /**
-     * Set whether an unocclusion animation is currently running on the notification panel. Used
-     * to avoid bright flickers of the notification scrim.
-     */
-    public void setUnocclusionAnimationRunning(boolean unocclusionAnimationRunning) {
-        mUnOcclusionAnimationRunning = unocclusionAnimationRunning;
-    }
-
     @IntDef(prefix = {"VISIBILITY_"}, value = {
             TRANSPARENT,
             SEMI_TRANSPARENT,
@@ -532,10 +518,6 @@
         }
     }
 
-    public void onExpandingFinished() {
-        setUnocclusionAnimationRunning(false);
-    }
-
     @VisibleForTesting
     protected void onHideWallpaperTimeout() {
         if (mState != ScrimState.AOD && mState != ScrimState.PULSING) {
@@ -875,13 +857,6 @@
             if (mKeyguardOccluded || hideNotificationScrim) {
                 mNotificationsAlpha = 0;
             }
-            if (mUnOcclusionAnimationRunning && mState == ScrimState.KEYGUARD) {
-                // We're unoccluding the keyguard and don't want to have a bright flash.
-                mNotificationsAlpha = ScrimState.KEYGUARD.getNotifAlpha();
-                mNotificationsTint = ScrimState.KEYGUARD.getNotifTint();
-                mBehindAlpha = ScrimState.KEYGUARD.getBehindAlpha();
-                mBehindTint = ScrimState.KEYGUARD.getBehindTint();
-            }
         }
         if (mState != ScrimState.UNLOCKED) {
             mAnimatingPanelExpansionOnUnlock = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index a127139..66f5b65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -281,7 +281,6 @@
     private float mQsExpansion;
     final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
     private boolean mIsModernAlternateBouncerEnabled;
-    private boolean mIsUnoccludeTransitionFlagEnabled;
     private boolean mIsBackAnimationEnabled;
 
     private OnDismissAction mAfterKeyguardGoneAction;
@@ -361,7 +360,6 @@
                 .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
         mIsModernAlternateBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER);
         mAlternateBouncerInteractor = alternateBouncerInteractor;
-        mIsUnoccludeTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION);
         mIsBackAnimationEnabled =
                 featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM);
     }
@@ -880,11 +878,6 @@
             // by a FLAG_DISMISS_KEYGUARD_ACTIVITY.
             reset(isOccluding /* hideBouncerWhenShowing*/);
         }
-        if (!mIsUnoccludeTransitionFlagEnabled) {
-            if (animate && !isOccluded && isShowing && !primaryBouncerIsShowing()) {
-                mCentralSurfaces.animateKeyguardUnoccluding();
-            }
-        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 3471a46..726b234 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -561,10 +561,6 @@
             mLogger.logFullScreenIntentSuppressedByVR(entry);
             return;
         }
-        if (mFeatureFlags.isEnabled(Flags.FSI_CHROME)) {
-            // FsiChromeRepo runs its own implementation of launchFullScreenIntent
-            return;
-        }
         // Stop screensaver if the notification has a fullscreen intent.
         // (like an incoming phone call)
         mUiBgExecutor.execute(() -> {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index dfad15d..7144914 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -26,7 +26,6 @@
 
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.ClockAnimations;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -61,8 +60,6 @@
     @Mock
     DozeParameters mDozeParameters;
     @Mock
-    FeatureFlags mFeatureFlags;
-    @Mock
     ScreenOffAnimationController mScreenOffAnimationController;
     @Captor
     private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
@@ -83,7 +80,6 @@
                 mKeyguardUpdateMonitor,
                 mConfigurationController,
                 mDozeParameters,
-                mFeatureFlags,
                 mScreenOffAnimationController,
                 mKeyguardLogger);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 7c20e3c..c93e677 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -29,7 +29,6 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -67,7 +66,6 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
@@ -136,7 +134,6 @@
     private @Mock SysuiColorExtractor mColorExtractor;
     private @Mock AuthController mAuthController;
     private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
-    private @Mock FeatureFlags mFeatureFlags;
     private @Mock ShadeWindowLogger mShadeWindowLogger;
     private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -545,7 +542,6 @@
                 mScreenOnCoordinator,
                 mInteractionJankMonitor,
                 mDreamOverlayStateController,
-                mFeatureFlags,
                 () -> mShadeController,
                 () -> mNotificationShadeWindowController,
                 () -> mActivityLaunchAnimator,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 39c4e06..0f0b7d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -23,16 +23,15 @@
 import android.os.UserManager
 import android.test.suitebuilder.annotation.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.notetask.NoteTaskController.Companion.INTENT_EXTRA_USE_STYLUS_MODE
-import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
-import com.android.systemui.notetask.NoteTaskInfoResolver.NoteTaskInfo
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
+import com.android.wm.shell.bubbles.Bubble
 import com.android.wm.shell.bubbles.Bubbles
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
@@ -44,12 +43,7 @@
 import org.mockito.Mockito.verifyZeroInteractions
 import org.mockito.MockitoAnnotations
 
-/**
- * Tests for [NoteTaskController].
- *
- * Build/Install/Run:
- * - atest SystemUITests:NoteTaskControllerTest
- */
+/** atest SystemUITests:NoteTaskControllerTest */
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 internal class NoteTaskControllerTest : SysuiTestCase() {
@@ -58,46 +52,163 @@
     @Mock lateinit var packageManager: PackageManager
     @Mock lateinit var resolver: NoteTaskInfoResolver
     @Mock lateinit var bubbles: Bubbles
-    @Mock lateinit var optionalBubbles: Optional<Bubbles>
     @Mock lateinit var keyguardManager: KeyguardManager
-    @Mock lateinit var optionalKeyguardManager: Optional<KeyguardManager>
-    @Mock lateinit var optionalUserManager: Optional<UserManager>
     @Mock lateinit var userManager: UserManager
-    @Mock lateinit var uiEventLogger: UiEventLogger
+    @Mock lateinit var eventLogger: NoteTaskEventLogger
+
+    private val noteTaskInfo = NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID)
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
         whenever(context.packageManager).thenReturn(packageManager)
-        whenever(resolver.resolveInfo()).thenReturn(NoteTaskInfo(NOTES_PACKAGE_NAME, NOTES_UID))
-        whenever(optionalBubbles.orElse(null)).thenReturn(bubbles)
-        whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager)
-        whenever(optionalUserManager.orElse(null)).thenReturn(userManager)
+        whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(noteTaskInfo)
         whenever(userManager.isUserUnlocked).thenReturn(true)
     }
 
-    private fun createNoteTaskController(isEnabled: Boolean = true): NoteTaskController {
-        return NoteTaskController(
+    private fun createNoteTaskController(
+        isEnabled: Boolean = true,
+        bubbles: Bubbles? = this.bubbles,
+        keyguardManager: KeyguardManager? = this.keyguardManager,
+        userManager: UserManager? = this.userManager,
+    ): NoteTaskController =
+        NoteTaskController(
             context = context,
             resolver = resolver,
-            optionalBubbles = optionalBubbles,
-            optionalKeyguardManager = optionalKeyguardManager,
-            optionalUserManager = optionalUserManager,
+            eventLogger = eventLogger,
+            optionalBubbles = Optional.ofNullable(bubbles),
+            optionalUserManager = Optional.ofNullable(userManager),
+            optionalKeyguardManager = Optional.ofNullable(keyguardManager),
             isEnabled = isEnabled,
-            uiEventLogger = uiEventLogger,
         )
+
+    // region onBubbleExpandChanged
+    @Test
+    fun onBubbleExpandChanged_expanding_logNoteTaskOpened() {
+        val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false, isInMultiWindowMode = false)
+
+        createNoteTaskController()
+            .apply { infoReference.set(expectedInfo) }
+            .onBubbleExpandChanged(
+                isExpanding = true,
+                key = Bubble.KEY_APP_BUBBLE,
+            )
+
+        verify(eventLogger).logNoteTaskOpened(expectedInfo)
+        verifyZeroInteractions(context, bubbles, keyguardManager, userManager)
     }
 
+    @Test
+    fun onBubbleExpandChanged_collapsing_logNoteTaskClosed() {
+        val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false, isInMultiWindowMode = false)
+
+        createNoteTaskController()
+            .apply { infoReference.set(expectedInfo) }
+            .onBubbleExpandChanged(
+                isExpanding = false,
+                key = Bubble.KEY_APP_BUBBLE,
+            )
+
+        verify(eventLogger).logNoteTaskClosed(expectedInfo)
+        verifyZeroInteractions(context, bubbles, keyguardManager, userManager)
+    }
+
+    @Test
+    fun onBubbleExpandChanged_expandingAndKeyguardLocked_doNothing() {
+        val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = true, isInMultiWindowMode = false)
+
+        createNoteTaskController()
+            .apply { infoReference.set(expectedInfo) }
+            .onBubbleExpandChanged(
+                isExpanding = true,
+                key = Bubble.KEY_APP_BUBBLE,
+            )
+
+        verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+    }
+
+    @Test
+    fun onBubbleExpandChanged_notExpandingAndKeyguardLocked_doNothing() {
+        val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = true, isInMultiWindowMode = false)
+
+        createNoteTaskController()
+            .apply { infoReference.set(expectedInfo) }
+            .onBubbleExpandChanged(
+                isExpanding = false,
+                key = Bubble.KEY_APP_BUBBLE,
+            )
+
+        verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+    }
+
+    @Test
+    fun onBubbleExpandChanged_expandingAndInMultiWindowMode_doNothing() {
+        val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false, isInMultiWindowMode = true)
+
+        createNoteTaskController()
+            .apply { infoReference.set(expectedInfo) }
+            .onBubbleExpandChanged(
+                isExpanding = true,
+                key = Bubble.KEY_APP_BUBBLE,
+            )
+
+        verifyZeroInteractions(context, bubbles, keyguardManager, userManager)
+    }
+
+    @Test
+    fun onBubbleExpandChanged_notExpandingAndInMultiWindowMode_doNothing() {
+        val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false, isInMultiWindowMode = true)
+
+        createNoteTaskController()
+            .apply { infoReference.set(expectedInfo) }
+            .onBubbleExpandChanged(
+                isExpanding = false,
+                key = Bubble.KEY_APP_BUBBLE,
+            )
+
+        verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+    }
+
+    @Test
+    fun onBubbleExpandChanged_notKeyAppBubble_shouldDoNothing() {
+        createNoteTaskController()
+            .onBubbleExpandChanged(
+                isExpanding = true,
+                key = "any other key",
+            )
+
+        verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+    }
+
+    @Test
+    fun onBubbleExpandChanged_flagDisabled_shouldDoNothing() {
+        createNoteTaskController(isEnabled = false)
+            .onBubbleExpandChanged(
+                isExpanding = true,
+                key = Bubble.KEY_APP_BUBBLE,
+            )
+
+        verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+    }
+    // endregion
+
     // region showNoteTask
     @Test
     fun showNoteTask_keyguardIsLocked_shouldStartActivityAndLogUiEvent() {
-        whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
+        val expectedInfo =
+            noteTaskInfo.copy(
+                entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
+                isInMultiWindowMode = false,
+                isKeyguardLocked = true,
+            )
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
+        whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
 
         createNoteTaskController()
             .showNoteTask(
-                isInMultiWindowMode = false,
-                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE,
+                entryPoint = expectedInfo.entryPoint!!,
+                isInMultiWindowMode = expectedInfo.isInMultiWindowMode,
             )
 
         val intentCaptor = argumentCaptor<Intent>()
@@ -108,23 +219,25 @@
             assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
             assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
         }
+        verify(eventLogger).logNoteTaskOpened(expectedInfo)
         verifyZeroInteractions(bubbles)
-        verify(uiEventLogger)
-            .log(
-                ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE,
-                NOTES_UID,
-                NOTES_PACKAGE_NAME
-            )
     }
 
     @Test
-    fun showNoteTask_keyguardIsUnlocked_shouldStartBubblesAndLogUiEvent() {
-        whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
+    fun showNoteTask_keyguardIsUnlocked_shouldStartBubblesWithoutLoggingUiEvent() {
+        val expectedInfo =
+            noteTaskInfo.copy(
+                entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
+                isInMultiWindowMode = false,
+                isKeyguardLocked = false,
+            )
+        whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
 
         createNoteTaskController()
             .showNoteTask(
-                isInMultiWindowMode = false,
-                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
+                entryPoint = expectedInfo.entryPoint!!,
+                isInMultiWindowMode = expectedInfo.isInMultiWindowMode,
             )
 
         verifyZeroInteractions(context)
@@ -136,40 +249,24 @@
             assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
             assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
         }
-        verify(uiEventLogger)
-            .log(
-                ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
-                NOTES_UID,
-                NOTES_PACKAGE_NAME
-            )
-    }
-
-    @Test
-    fun showNoteTask_keyguardIsUnlocked_uiEventIsNull_shouldStartBubblesWithoutLoggingUiEvent() {
-        whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
-
-        createNoteTaskController().showNoteTask(isInMultiWindowMode = false, uiEvent = null)
-
-        verifyZeroInteractions(context)
-        val intentCaptor = argumentCaptor<Intent>()
-        verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
-        intentCaptor.value.let { intent ->
-            assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
-            assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
-            assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
-            assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
-        }
-        verifyZeroInteractions(uiEventLogger)
+        verifyZeroInteractions(eventLogger)
     }
 
     @Test
     fun showNoteTask_isInMultiWindowMode_shouldStartActivityAndLogUiEvent() {
-        whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
+        val expectedInfo =
+            noteTaskInfo.copy(
+                entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT,
+                isInMultiWindowMode = true,
+                isKeyguardLocked = false,
+            )
+        whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
 
         createNoteTaskController()
             .showNoteTask(
-                isInMultiWindowMode = true,
-                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT,
+                entryPoint = expectedInfo.entryPoint!!,
+                isInMultiWindowMode = expectedInfo.isInMultiWindowMode,
             )
 
         val intentCaptor = argumentCaptor<Intent>()
@@ -180,69 +277,65 @@
             assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
             assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
         }
+        verify(eventLogger).logNoteTaskOpened(expectedInfo)
         verifyZeroInteractions(bubbles)
-        verify(uiEventLogger)
-            .log(ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT, NOTES_UID, NOTES_PACKAGE_NAME)
     }
 
     @Test
     fun showNoteTask_bubblesIsNull_shouldDoNothing() {
-        whenever(optionalBubbles.orElse(null)).thenReturn(null)
-
-        createNoteTaskController()
+        createNoteTaskController(bubbles = null)
             .showNoteTask(
+                entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
                 isInMultiWindowMode = false,
-                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
             )
 
-        verifyZeroInteractions(context, bubbles, uiEventLogger)
+        verifyZeroInteractions(context, bubbles, eventLogger)
     }
 
     @Test
     fun showNoteTask_keyguardManagerIsNull_shouldDoNothing() {
-        whenever(optionalKeyguardManager.orElse(null)).thenReturn(null)
-
-        createNoteTaskController()
+        createNoteTaskController(keyguardManager = null)
             .showNoteTask(
+                entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
                 isInMultiWindowMode = false,
-                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
             )
 
-        verifyZeroInteractions(context, bubbles, uiEventLogger)
+        verifyZeroInteractions(context, bubbles, eventLogger)
     }
 
     @Test
     fun showNoteTask_userManagerIsNull_shouldDoNothing() {
-        whenever(optionalUserManager.orElse(null)).thenReturn(null)
-
-        createNoteTaskController()
+        createNoteTaskController(userManager = null)
             .showNoteTask(
+                entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
                 isInMultiWindowMode = false,
-                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
             )
 
-        verifyZeroInteractions(context, bubbles, uiEventLogger)
+        verifyZeroInteractions(context, bubbles, eventLogger)
     }
 
     @Test
     fun showNoteTask_intentResolverReturnsNull_shouldDoNothing() {
-        whenever(resolver.resolveInfo()).thenReturn(null)
+        whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(null)
 
         createNoteTaskController()
             .showNoteTask(
+                entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
                 isInMultiWindowMode = false,
-                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
             )
 
-        verifyZeroInteractions(context, bubbles, uiEventLogger)
+        verifyZeroInteractions(context, bubbles, eventLogger)
     }
 
     @Test
     fun showNoteTask_flagDisabled_shouldDoNothing() {
         createNoteTaskController(isEnabled = false)
-            .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON)
+            .showNoteTask(
+                entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
+                isInMultiWindowMode = false,
+            )
 
-        verifyZeroInteractions(context, bubbles, uiEventLogger)
+        verifyZeroInteractions(context, bubbles, eventLogger)
     }
 
     @Test
@@ -251,11 +344,11 @@
 
         createNoteTaskController()
             .showNoteTask(
+                entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
                 isInMultiWindowMode = false,
-                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
             )
 
-        verifyZeroInteractions(context, bubbles, uiEventLogger)
+        verifyZeroInteractions(context, bubbles, eventLogger)
     }
     // endregion
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
new file mode 100644
index 0000000..a4df346
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
@@ -0,0 +1,160 @@
+/*
+ * 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.notetask
+
+import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+/** atest SystemUITests:MonitoringNoteTaskEventListenerTest */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class NoteTaskEventLoggerTest : SysuiTestCase() {
+
+    @Mock lateinit var uiEventLogger: UiEventLogger
+
+    private fun createNoteTaskEventLogger(): NoteTaskEventLogger =
+        NoteTaskEventLogger(uiEventLogger)
+
+    private fun createNoteTaskInfo(): NoteTaskInfo =
+        NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    // region logNoteTaskOpened
+    @Test
+    fun logNoteTaskOpened_entryPointWidgetPickerShortcut_noteOpenedViaShortcut() {
+        val info = createNoteTaskInfo().copy(entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT)
+
+        createNoteTaskEventLogger().logNoteTaskOpened(info)
+
+        val expected = NOTE_OPENED_VIA_SHORTCUT
+        verify(uiEventLogger).log(expected, info.uid, info.packageName)
+    }
+
+    @Test
+    fun onNoteTaskBubbleExpanded_entryPointQuickAffordance_noteOpenedViaQuickAffordance() {
+        val info = createNoteTaskInfo().copy(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
+
+        createNoteTaskEventLogger().logNoteTaskOpened(info)
+
+        val expected = NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
+        verify(uiEventLogger).log(expected, info.uid, info.packageName)
+    }
+
+    @Test
+    fun onNoteTaskBubbleExpanded_entryPointTailButtonAndIsKeyguardUnlocked_noteOpenedViaTailButtonUnlocked() { // ktlint-disable max-line-length
+        val info =
+            createNoteTaskInfo()
+                .copy(
+                    entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
+                    isKeyguardLocked = false,
+                )
+
+        createNoteTaskEventLogger().logNoteTaskOpened(info)
+
+        val expected = NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
+        verify(uiEventLogger).log(expected, info.uid, info.packageName)
+    }
+
+    @Test
+    fun onNoteTaskBubbleExpanded_entryPointTailButtonAndIsKeyguardLocked_noteOpenedViaTailButtonLocked() { // ktlint-disable max-line-length
+        val info =
+            createNoteTaskInfo()
+                .copy(
+                    entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
+                    isKeyguardLocked = true,
+                )
+
+        createNoteTaskEventLogger().logNoteTaskOpened(info)
+
+        val expected = NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+        verify(uiEventLogger).log(expected, info.uid, info.packageName)
+    }
+
+    @Test
+    fun onNoteTaskBubbleExpanded_noEntryPoint_noLog() {
+        val info = createNoteTaskInfo().copy(entryPoint = null)
+
+        createNoteTaskEventLogger().logNoteTaskOpened(info)
+
+        verifyNoMoreInteractions(uiEventLogger)
+    }
+    // endregion
+
+    // region logNoteTaskClosed
+    @Test
+    fun logNoteTaskClosed_entryPointTailButton_noteClosedViaTailButtonUnlocked() {
+        val info =
+            createNoteTaskInfo()
+                .copy(
+                    entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
+                    isKeyguardLocked = false,
+                )
+
+        createNoteTaskEventLogger().logNoteTaskClosed(info)
+
+        val expected = NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON
+        verify(uiEventLogger).log(expected, info.uid, info.packageName)
+    }
+
+    @Test
+    fun logNoteTaskClosed_entryPointTailButtonAndKeyguardLocked_noteClosedViaTailButtonLocked() {
+        val info =
+            createNoteTaskInfo()
+                .copy(
+                    entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
+                    isKeyguardLocked = true,
+                )
+
+        createNoteTaskEventLogger().logNoteTaskClosed(info)
+
+        val expected = NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+        verify(uiEventLogger).log(expected, info.uid, info.packageName)
+    }
+
+    @Test
+    fun logNoteTaskClosed_noEntryPoint_noLog() {
+        val info = createNoteTaskInfo().copy(entryPoint = null)
+
+        createNoteTaskEventLogger().logNoteTaskOpened(info)
+
+        verifyNoMoreInteractions(uiEventLogger)
+    }
+    // endregion
+
+    private companion object {
+        const val NOTES_PACKAGE_NAME = "com.android.note.app"
+        const val NOTES_UID = 123456
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
new file mode 100644
index 0000000..7e975b6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.notetask
+
+import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** atest SystemUITests:NoteTaskInfoTest */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class NoteTaskInfoTest : SysuiTestCase() {
+
+    private fun createNoteTaskInfo(): NoteTaskInfo =
+        NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID)
+
+    @Test
+    fun launchMode_notInMultiWindowModeAndKeyguardUnlocked_launchModeAppBubble() {
+        val underTest =
+            createNoteTaskInfo()
+                .copy(
+                    isKeyguardLocked = false,
+                    isInMultiWindowMode = false,
+                )
+
+        assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.AppBubble)
+    }
+
+    @Test
+    fun launchMode_inMultiWindowMode_launchModeActivity() {
+        val underTest =
+            createNoteTaskInfo()
+                .copy(
+                    isKeyguardLocked = false,
+                    isInMultiWindowMode = true,
+                )
+
+        assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.Activity)
+    }
+
+    @Test
+    fun launchMode_keyguardLocked_launchModeActivity() {
+        val underTest =
+            createNoteTaskInfo()
+                .copy(
+                    isKeyguardLocked = true,
+                    isInMultiWindowMode = false,
+                )
+
+        assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.Activity)
+    }
+
+    private companion object {
+        const val NOTES_PACKAGE_NAME = "com.android.note.app"
+        const val NOTES_UID = 123456
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index 53720ff..a8eab50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -15,15 +15,11 @@
  */
 package com.android.systemui.notetask
 
-import android.app.KeyguardManager
 import android.test.suitebuilder.annotation.SmallTest
 import android.view.KeyEvent
 import androidx.test.runner.AndroidJUnit4
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
 import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
 import com.android.wm.shell.bubbles.Bubbles
 import java.util.Optional
 import org.junit.Before
@@ -48,27 +44,22 @@
 
     @Mock lateinit var commandQueue: CommandQueue
     @Mock lateinit var bubbles: Bubbles
-    @Mock lateinit var optionalBubbles: Optional<Bubbles>
     @Mock lateinit var noteTaskController: NoteTaskController
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-
-        whenever(optionalBubbles.isPresent).thenReturn(true)
-        whenever(optionalBubbles.orElse(null)).thenReturn(bubbles)
     }
 
     private fun createNoteTaskInitializer(
         isEnabled: Boolean = true,
-        optionalKeyguardManager: Optional<KeyguardManager> = Optional.empty(),
+        bubbles: Bubbles? = this.bubbles,
     ): NoteTaskInitializer {
         return NoteTaskInitializer(
-            optionalBubbles = optionalBubbles,
-            noteTaskController = noteTaskController,
+            controller = noteTaskController,
             commandQueue = commandQueue,
+            optionalBubbles = Optional.ofNullable(bubbles),
             isEnabled = isEnabled,
-            optionalKeyguardManager = optionalKeyguardManager,
         )
     }
 
@@ -89,9 +80,7 @@
 
     @Test
     fun initialize_bubblesNotPresent_shouldDoNothing() {
-        whenever(optionalBubbles.isPresent).thenReturn(false)
-
-        createNoteTaskInitializer().initialize()
+        createNoteTaskInitializer(bubbles = null).initialize()
 
         verify(commandQueue, never()).addCallback(any())
     }
@@ -113,37 +102,12 @@
 
     // region handleSystemKey
     @Test
-    fun handleSystemKey_receiveValidSystemKey_keyguardNotLocked_shouldShowNoteTaskWithUnlocked() {
-        val keyguardManager =
-            mock<KeyguardManager>() { whenever(isKeyguardLocked).thenReturn(false) }
-        createNoteTaskInitializer(optionalKeyguardManager = Optional.of(keyguardManager))
+    fun handleSystemKey_receiveValidSystemKey_shouldShowNoteTask() {
+        createNoteTaskInitializer()
             .callbacks
             .handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT)
 
-        verify(noteTaskController)
-            .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON)
-    }
-
-    @Test
-    fun handleSystemKey_receiveValidSystemKey_keyguardLocked_shouldShowNoteTaskWithLocked() {
-        val keyguardManager =
-            mock<KeyguardManager>() { whenever(isKeyguardLocked).thenReturn(true) }
-        createNoteTaskInitializer(optionalKeyguardManager = Optional.of(keyguardManager))
-            .callbacks
-            .handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT)
-
-        verify(noteTaskController)
-            .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED)
-    }
-
-    @Test
-    fun handleSystemKey_receiveValidSystemKey_nullKeyguardManager_shouldShowNoteTaskWithUnlocked() {
-        createNoteTaskInitializer(optionalKeyguardManager = Optional.empty())
-            .callbacks
-            .handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT)
-
-        verify(noteTaskController)
-            .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON)
+        verify(noteTaskController).showNoteTask(entryPoint = NoteTaskEntryPoint.TAIL_BUTTON)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
index cdc683f..e57d0d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
@@ -27,7 +27,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState
 import com.android.systemui.notetask.NoteTaskController
-import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
+import com.android.systemui.notetask.NoteTaskEntryPoint
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
@@ -95,7 +95,6 @@
 
         underTest.onTriggered(expandable = null)
 
-        verify(noteTaskController)
-            .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE)
+        verify(noteTaskController).showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
new file mode 100644
index 0000000..52b0b6a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -0,0 +1,753 @@
+/*
+ * 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.shade;
+
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
+import static com.android.keyguard.KeyguardClockSwitch.LARGE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.IdRes;
+import android.content.ContentResolver;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.UserManager;
+import android.util.DisplayMetrics;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.ViewPropertyAnimator;
+import android.view.ViewStub;
+import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.constraintlayout.widget.ConstraintSet;
+
+import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.internal.util.LatencyTracker;
+import com.android.keyguard.KeyguardClockSwitch;
+import com.android.keyguard.KeyguardClockSwitchController;
+import com.android.keyguard.KeyguardStatusView;
+import com.android.keyguard.KeyguardStatusViewController;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.LockIconViewController;
+import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
+import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
+import com.android.keyguard.dagger.KeyguardStatusViewComponent;
+import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.classifier.FalsingCollectorFake;
+import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.common.ui.view.LongPressHandlingView;
+import com.android.systemui.doze.DozeLog;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.fragments.FragmentService;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository;
+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.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
+import com.android.systemui.media.controls.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.ui.KeyguardMediaController;
+import com.android.systemui.media.controls.ui.MediaHierarchyManager;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.qs.QSFragment;
+import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.shade.transition.ShadeTransitionController;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.NotificationShelfController;
+import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.QsFrameTranslateController;
+import com.android.systemui.statusbar.StatusBarStateControllerImpl;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.notification.ConversationNotificationManager;
+import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.stack.AmbientState;
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+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.NotificationStackSizeCalculator;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
+import com.android.systemui.statusbar.phone.TapAgainViewController;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.util.time.SystemClock;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+
+import java.util.List;
+import java.util.Optional;
+
+import dagger.Lazy;
+import kotlinx.coroutines.CoroutineDispatcher;
+
+public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
+
+    protected static final int SPLIT_SHADE_FULL_TRANSITION_DISTANCE = 400;
+    protected static final int NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE = 50;
+    protected static final int PANEL_WIDTH = 500; // Random value just for the test.
+
+    @Mock protected CentralSurfaces mCentralSurfaces;
+    @Mock protected NotificationStackScrollLayout mNotificationStackScrollLayout;
+    @Mock protected KeyguardBottomAreaView mKeyguardBottomArea;
+    @Mock protected KeyguardBottomAreaViewController mKeyguardBottomAreaViewController;
+    @Mock protected KeyguardBottomAreaView mQsFrame;
+    @Mock protected HeadsUpManagerPhone mHeadsUpManager;
+    @Mock protected NotificationShelfController mNotificationShelfController;
+    @Mock protected NotificationGutsManager mGutsManager;
+    @Mock protected KeyguardStatusBarView mKeyguardStatusBar;
+    @Mock protected KeyguardUserSwitcherView mUserSwitcherView;
+    @Mock protected ViewStub mUserSwitcherStubView;
+    @Mock protected HeadsUpTouchHelper.Callback mHeadsUpCallback;
+    @Mock protected KeyguardUpdateMonitor mUpdateMonitor;
+    @Mock protected KeyguardBypassController mKeyguardBypassController;
+    @Mock protected DozeParameters mDozeParameters;
+    @Mock protected ScreenOffAnimationController mScreenOffAnimationController;
+    @Mock protected NotificationPanelView mView;
+    @Mock protected LayoutInflater mLayoutInflater;
+    @Mock protected FeatureFlags mFeatureFlags;
+    @Mock protected DynamicPrivacyController mDynamicPrivacyController;
+    @Mock protected StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
+    @Mock protected KeyguardStateController mKeyguardStateController;
+    @Mock protected DozeLog mDozeLog;
+    @Mock protected ShadeLogger mShadeLog;
+    @Mock protected ShadeHeightLogger mShadeHeightLogger;
+    @Mock protected CommandQueue mCommandQueue;
+    @Mock protected VibratorHelper mVibratorHelper;
+    @Mock protected LatencyTracker mLatencyTracker;
+    @Mock protected PowerManager mPowerManager;
+    @Mock protected AccessibilityManager mAccessibilityManager;
+    @Mock protected MetricsLogger mMetricsLogger;
+    @Mock protected Resources mResources;
+    @Mock protected Configuration mConfiguration;
+    @Mock protected KeyguardClockSwitch mKeyguardClockSwitch;
+    @Mock protected MediaHierarchyManager mMediaHierarchyManager;
+    @Mock protected ConversationNotificationManager mConversationNotificationManager;
+    @Mock protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    @Mock protected KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
+    @Mock protected KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory;
+    @Mock protected KeyguardQsUserSwitchComponent mKeyguardQsUserSwitchComponent;
+    @Mock protected KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
+    @Mock protected KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory;
+    @Mock protected KeyguardUserSwitcherComponent mKeyguardUserSwitcherComponent;
+    @Mock protected KeyguardUserSwitcherController mKeyguardUserSwitcherController;
+    @Mock protected KeyguardStatusViewComponent mKeyguardStatusViewComponent;
+    @Mock protected KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
+    @Mock protected KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent;
+    @Mock protected KeyguardClockSwitchController mKeyguardClockSwitchController;
+    @Mock protected KeyguardStatusViewController mKeyguardStatusViewController;
+    @Mock protected KeyguardStatusBarViewController mKeyguardStatusBarViewController;
+    @Mock protected NotificationStackScrollLayoutController
+            mNotificationStackScrollLayoutController;
+    @Mock protected NotificationShadeDepthController mNotificationShadeDepthController;
+    @Mock protected LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+    @Mock protected AuthController mAuthController;
+    @Mock protected ScrimController mScrimController;
+    @Mock protected MediaDataManager mMediaDataManager;
+    @Mock protected AmbientState mAmbientState;
+    @Mock protected UserManager mUserManager;
+    @Mock protected UiEventLogger mUiEventLogger;
+    @Mock protected LockIconViewController mLockIconViewController;
+    @Mock protected KeyguardMediaController mKeyguardMediaController;
+    @Mock protected NavigationModeController mNavigationModeController;
+    @Mock protected NavigationBarController mNavigationBarController;
+    @Mock protected QuickSettingsController mQsController;
+    @Mock protected ShadeHeaderController mShadeHeaderController;
+    @Mock protected ContentResolver mContentResolver;
+    @Mock protected TapAgainViewController mTapAgainViewController;
+    @Mock protected KeyguardIndicationController mKeyguardIndicationController;
+    @Mock protected FragmentService mFragmentService;
+    @Mock protected FragmentHostManager mFragmentHostManager;
+    @Mock protected NotificationRemoteInputManager mNotificationRemoteInputManager;
+    @Mock protected RecordingController mRecordingController;
+    @Mock protected LockscreenGestureLogger mLockscreenGestureLogger;
+    @Mock protected DumpManager mDumpManager;
+    @Mock protected InteractionJankMonitor mInteractionJankMonitor;
+    @Mock protected NotificationsQSContainerController mNotificationsQSContainerController;
+    @Mock protected QsFrameTranslateController mQsFrameTranslateController;
+    @Mock protected StatusBarWindowStateController mStatusBarWindowStateController;
+    @Mock protected KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+    @Mock protected NotificationShadeWindowController mNotificationShadeWindowController;
+    @Mock protected SysUiState mSysUiState;
+    @Mock protected NotificationListContainer mNotificationListContainer;
+    @Mock protected NotificationStackSizeCalculator mNotificationStackSizeCalculator;
+    @Mock protected UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    @Mock protected ShadeTransitionController mShadeTransitionController;
+    @Mock protected QS mQs;
+    @Mock protected QSFragment mQSFragment;
+    @Mock protected ViewGroup mQsHeader;
+    @Mock protected ViewParent mViewParent;
+    @Mock protected ViewTreeObserver mViewTreeObserver;
+    @Mock protected KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
+    @Mock protected DreamingToLockscreenTransitionViewModel
+            mDreamingToLockscreenTransitionViewModel;
+    @Mock protected OccludedToLockscreenTransitionViewModel
+            mOccludedToLockscreenTransitionViewModel;
+    @Mock protected LockscreenToDreamingTransitionViewModel
+            mLockscreenToDreamingTransitionViewModel;
+    @Mock protected LockscreenToOccludedTransitionViewModel
+            mLockscreenToOccludedTransitionViewModel;
+    @Mock protected GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel;
+
+    @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    @Mock protected KeyguardLongPressViewModel mKeyuardLongPressViewModel;
+    @Mock protected AlternateBouncerInteractor mAlternateBouncerInteractor;
+    @Mock protected MotionEvent mDownMotionEvent;
+    @Mock protected CoroutineDispatcher mMainDispatcher;
+    @Captor
+    protected ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener>
+            mEmptySpaceClickListenerCaptor;
+
+    protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
+    protected KeyguardInteractor mKeyguardInteractor;
+    protected NotificationPanelViewController.TouchHandler mTouchHandler;
+    protected ConfigurationController mConfigurationController;
+    protected SysuiStatusBarStateController mStatusBarStateController;
+    protected NotificationPanelViewController mNotificationPanelViewController;
+    protected View.AccessibilityDelegate mAccessibilityDelegate;
+    protected NotificationsQuickSettingsContainer mNotificationContainerParent;
+    protected List<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners;
+    protected Handler mMainHandler;
+    protected View.OnLayoutChangeListener mLayoutChangeListener;
+
+    protected final FalsingManagerFake mFalsingManager = new FalsingManagerFake();
+    protected final Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
+    protected final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+    protected final ShadeExpansionStateManager mShadeExpansionStateManager =
+            new ShadeExpansionStateManager();
+
+    protected QuickSettingsController mQuickSettingsController;
+    @Mock protected Lazy<NotificationPanelViewController> mNotificationPanelViewControllerLazy;
+
+    protected FragmentHostManager.FragmentListener mFragmentListener;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mMainDispatcher = getMainDispatcher();
+        mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor(
+                new FakeKeyguardRepository());
+        mKeyguardInteractor = new KeyguardInteractor(new FakeKeyguardRepository(), mCommandQueue,
+                mFeatureFlags, new FakeKeyguardBouncerRepository());
+        SystemClock systemClock = new FakeSystemClock();
+        mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager,
+                mInteractionJankMonitor, mShadeExpansionStateManager);
+
+        KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
+        keyguardStatusView.setId(R.id.keyguard_status_view);
+
+        when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
+        when(mHeadsUpCallback.getContext()).thenReturn(mContext);
+        when(mView.getResources()).thenReturn(mResources);
+        when(mView.getWidth()).thenReturn(PANEL_WIDTH);
+        when(mResources.getConfiguration()).thenReturn(mConfiguration);
+        mConfiguration.orientation = ORIENTATION_PORTRAIT;
+        when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
+        mDisplayMetrics.density = 100;
+        when(mResources.getBoolean(R.bool.config_enableNotificationShadeDrag)).thenReturn(true);
+        when(mResources.getDimensionPixelSize(R.dimen.notifications_top_padding_split_shade))
+                .thenReturn(NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE);
+        when(mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal))
+                .thenReturn(10);
+        when(mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance))
+                .thenReturn(SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
+        when(mView.getContext()).thenReturn(getContext());
+        when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar);
+        when(mView.findViewById(R.id.keyguard_user_switcher_view)).thenReturn(mUserSwitcherView);
+        when(mView.findViewById(R.id.keyguard_user_switcher_stub)).thenReturn(
+                mUserSwitcherStubView);
+        when(mView.findViewById(R.id.keyguard_clock_container)).thenReturn(mKeyguardClockSwitch);
+        when(mView.findViewById(R.id.notification_stack_scroller))
+                .thenReturn(mNotificationStackScrollLayout);
+        when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(1000);
+        when(mNotificationStackScrollLayoutController.getHeadsUpCallback())
+                .thenReturn(mHeadsUpCallback);
+        when(mKeyguardBottomAreaViewController.getView()).thenReturn(mKeyguardBottomArea);
+        when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea);
+        when(mKeyguardBottomArea.animate()).thenReturn(mock(ViewPropertyAnimator.class));
+        when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
+        when(mView.findViewById(R.id.keyguard_status_view))
+                .thenReturn(mock(KeyguardStatusView.class));
+        mNotificationContainerParent = new NotificationsQuickSettingsContainer(getContext(), null);
+        mNotificationContainerParent.addView(keyguardStatusView);
+        mNotificationContainerParent.onFinishInflate();
+        when(mView.findViewById(R.id.notification_container_parent))
+                .thenReturn(mNotificationContainerParent);
+        when(mFragmentService.getFragmentHostManager(mView)).thenReturn(mFragmentHostManager);
+        FlingAnimationUtils.Builder flingAnimationUtilsBuilder = new FlingAnimationUtils.Builder(
+                mDisplayMetrics);
+        when(mKeyguardQsUserSwitchComponentFactory.build(any()))
+                .thenReturn(mKeyguardQsUserSwitchComponent);
+        when(mKeyguardQsUserSwitchComponent.getKeyguardQsUserSwitchController())
+                .thenReturn(mKeyguardQsUserSwitchController);
+        when(mKeyguardUserSwitcherComponentFactory.build(any()))
+                .thenReturn(mKeyguardUserSwitcherComponent);
+        when(mKeyguardUserSwitcherComponent.getKeyguardUserSwitcherController())
+                .thenReturn(mKeyguardUserSwitcherController);
+        when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(true);
+        when(mQs.getView()).thenReturn(mView);
+        when(mQSFragment.getView()).thenReturn(mView);
+        doAnswer(invocation -> {
+            mFragmentListener = invocation.getArgument(1);
+            return null;
+        }).when(mFragmentHostManager).addTagListener(eq(QS.TAG), any());
+        doAnswer((Answer<Void>) invocation -> {
+            mTouchHandler = invocation.getArgument(0);
+            return null;
+        }).when(mView).setOnTouchListener(any(NotificationPanelViewController.TouchHandler.class));
+
+        // Dreaming->Lockscreen
+        when(mKeyguardTransitionInteractor.getDreamingToLockscreenTransition())
+                .thenReturn(emptyFlow());
+        when(mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha())
+                .thenReturn(emptyFlow());
+        when(mDreamingToLockscreenTransitionViewModel.lockscreenTranslationY(anyInt()))
+                .thenReturn(emptyFlow());
+
+        // Occluded->Lockscreen
+        when(mKeyguardTransitionInteractor.getOccludedToLockscreenTransition())
+                .thenReturn(emptyFlow());
+        when(mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha())
+                .thenReturn(emptyFlow());
+        when(mOccludedToLockscreenTransitionViewModel.lockscreenTranslationY(anyInt()))
+                .thenReturn(emptyFlow());
+
+        // Lockscreen->Dreaming
+        when(mKeyguardTransitionInteractor.getLockscreenToDreamingTransition())
+                .thenReturn(emptyFlow());
+        when(mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha())
+                .thenReturn(emptyFlow());
+        when(mLockscreenToDreamingTransitionViewModel.lockscreenTranslationY(anyInt()))
+                .thenReturn(emptyFlow());
+
+        // Gone->Dreaming
+        when(mKeyguardTransitionInteractor.getGoneToDreamingTransition())
+                .thenReturn(emptyFlow());
+        when(mGoneToDreamingTransitionViewModel.getLockscreenAlpha())
+                .thenReturn(emptyFlow());
+        when(mGoneToDreamingTransitionViewModel.lockscreenTranslationY(anyInt()))
+                .thenReturn(emptyFlow());
+
+        // Lockscreen->Occluded
+        when(mKeyguardTransitionInteractor.getLockscreenToOccludedTransition())
+                .thenReturn(emptyFlow());
+        when(mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha())
+                .thenReturn(emptyFlow());
+        when(mLockscreenToOccludedTransitionViewModel.lockscreenTranslationY(anyInt()))
+                .thenReturn(emptyFlow());
+
+        NotificationWakeUpCoordinator coordinator =
+                new NotificationWakeUpCoordinator(
+                        mDumpManager,
+                        mock(HeadsUpManagerPhone.class),
+                        new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager,
+                                mInteractionJankMonitor, mShadeExpansionStateManager),
+                        mKeyguardBypassController,
+                        mDozeParameters,
+                        mScreenOffAnimationController,
+                        mock(NotificationWakeUpCoordinatorLogger.class));
+        mConfigurationController = new ConfigurationControllerImpl(mContext);
+        PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
+                mContext,
+                coordinator,
+                mKeyguardBypassController, mHeadsUpManager,
+                mock(NotificationRoundnessManager.class),
+                mConfigurationController,
+                mStatusBarStateController,
+                mFalsingManager,
+                mShadeExpansionStateManager,
+                mLockscreenShadeTransitionController,
+                new FalsingCollectorFake(),
+                mDumpManager);
+        when(mKeyguardStatusViewComponentFactory.build(any()))
+                .thenReturn(mKeyguardStatusViewComponent);
+        when(mKeyguardStatusViewComponent.getKeyguardClockSwitchController())
+                .thenReturn(mKeyguardClockSwitchController);
+        when(mKeyguardStatusViewComponent.getKeyguardStatusViewController())
+                .thenReturn(mKeyguardStatusViewController);
+        when(mKeyguardStatusBarViewComponentFactory.build(any(), any()))
+                .thenReturn(mKeyguardStatusBarViewComponent);
+        when(mKeyguardStatusBarViewComponent.getKeyguardStatusBarViewController())
+                .thenReturn(mKeyguardStatusBarViewController);
+        when(mLayoutInflater.inflate(eq(R.layout.keyguard_status_view), any(), anyBoolean()))
+                .thenReturn(keyguardStatusView);
+        when(mLayoutInflater.inflate(eq(R.layout.keyguard_user_switcher), any(), anyBoolean()))
+                .thenReturn(mUserSwitcherView);
+        when(mLayoutInflater.inflate(eq(R.layout.keyguard_bottom_area), any(), anyBoolean()))
+                .thenReturn(mKeyguardBottomArea);
+        when(mNotificationRemoteInputManager.isRemoteInputActive())
+                .thenReturn(false);
+        when(mInteractionJankMonitor.begin(any(), anyInt()))
+                .thenReturn(true);
+        when(mInteractionJankMonitor.end(anyInt()))
+                .thenReturn(true);
+        doAnswer(invocation -> {
+            ((Runnable) invocation.getArgument(0)).run();
+            return null;
+        }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any());
+        doAnswer(invocation -> {
+            mLayoutChangeListener = invocation.getArgument(0);
+            return null;
+        }).when(mView).addOnLayoutChangeListener(any());
+
+        when(mView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
+        when(mView.getParent()).thenReturn(mViewParent);
+        when(mQs.getHeader()).thenReturn(mQsHeader);
+        when(mDownMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN);
+        when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
+
+        mMainHandler = new Handler(Looper.getMainLooper());
+
+        when(mView.requireViewById(R.id.keyguard_long_press))
+                .thenReturn(mock(LongPressHandlingView.class));
+
+        mNotificationPanelViewController = new NotificationPanelViewController(
+                mView,
+                mMainHandler,
+                mLayoutInflater,
+                mFeatureFlags,
+                coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController,
+                mFalsingManager, new FalsingCollectorFake(),
+                mKeyguardStateController,
+                mStatusBarStateController,
+                mStatusBarWindowStateController,
+                mNotificationShadeWindowController,
+                mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper,
+                mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
+                mMetricsLogger,
+                mShadeLog,
+                mShadeHeightLogger,
+                mConfigurationController,
+                () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
+                mConversationNotificationManager, mMediaHierarchyManager,
+                mStatusBarKeyguardViewManager,
+                mGutsManager,
+                mNotificationsQSContainerController,
+                mNotificationStackScrollLayoutController,
+                mKeyguardStatusViewComponentFactory,
+                mKeyguardQsUserSwitchComponentFactory,
+                mKeyguardUserSwitcherComponentFactory,
+                mKeyguardStatusBarViewComponentFactory,
+                mLockscreenShadeTransitionController,
+                mAuthController,
+                mScrimController,
+                mUserManager,
+                mMediaDataManager,
+                mNotificationShadeDepthController,
+                mAmbientState,
+                mLockIconViewController,
+                mKeyguardMediaController,
+                mTapAgainViewController,
+                mNavigationModeController,
+                mNavigationBarController,
+                mQsController,
+                mFragmentService,
+                mContentResolver,
+                mRecordingController,
+                mShadeHeaderController,
+                mScreenOffAnimationController,
+                mLockscreenGestureLogger,
+                mShadeExpansionStateManager,
+                mNotificationRemoteInputManager,
+                mSysUIUnfoldComponent,
+                mSysUiState,
+                () -> mKeyguardBottomAreaViewController,
+                mKeyguardUnlockAnimationController,
+                mKeyguardIndicationController,
+                mNotificationListContainer,
+                mNotificationStackSizeCalculator,
+                mUnlockedScreenOffAnimationController,
+                mShadeTransitionController,
+                systemClock,
+                mKeyguardBottomAreaViewModel,
+                mKeyguardBottomAreaInteractor,
+                mAlternateBouncerInteractor,
+                mDreamingToLockscreenTransitionViewModel,
+                mOccludedToLockscreenTransitionViewModel,
+                mLockscreenToDreamingTransitionViewModel,
+                mGoneToDreamingTransitionViewModel,
+                mLockscreenToOccludedTransitionViewModel,
+                mMainDispatcher,
+                mKeyguardTransitionInteractor,
+                mDumpManager,
+                mKeyuardLongPressViewModel,
+                mKeyguardInteractor);
+        mNotificationPanelViewController.initDependencies(
+                mCentralSurfaces,
+                null,
+                () -> {},
+                mNotificationShelfController);
+        mNotificationPanelViewController.setTrackingStartedListener(() -> {});
+        mNotificationPanelViewController.setOpenCloseListener(
+                new NotificationPanelViewController.OpenCloseListener() {
+                    @Override
+                    public void onClosingFinished() {}
+
+                    @Override
+                    public void onOpenStarted() {}
+                });
+        mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
+        ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor =
+                ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+        verify(mView, atLeast(1)).addOnAttachStateChangeListener(
+                onAttachStateChangeListenerArgumentCaptor.capture());
+        mOnAttachStateChangeListeners = onAttachStateChangeListenerArgumentCaptor.getAllValues();
+
+        ArgumentCaptor<View.AccessibilityDelegate> accessibilityDelegateArgumentCaptor =
+                ArgumentCaptor.forClass(View.AccessibilityDelegate.class);
+        verify(mView).setAccessibilityDelegate(accessibilityDelegateArgumentCaptor.capture());
+        mAccessibilityDelegate = accessibilityDelegateArgumentCaptor.getValue();
+        mNotificationPanelViewController.getStatusBarStateController()
+                .addCallback(mNotificationPanelViewController.getStatusBarStateListener());
+        mNotificationPanelViewController
+                .setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class));
+        verify(mNotificationStackScrollLayoutController)
+                .setOnEmptySpaceClickListener(mEmptySpaceClickListenerCaptor.capture());
+        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
+        reset(mKeyguardStatusViewController);
+
+        when(mNotificationPanelViewControllerLazy.get())
+                .thenReturn(mNotificationPanelViewController);
+        mQuickSettingsController = new QuickSettingsController(
+                mNotificationPanelViewControllerLazy,
+                mView,
+                mQsFrameTranslateController,
+                mShadeTransitionController,
+                expansionHandler,
+                mNotificationRemoteInputManager,
+                mShadeExpansionStateManager,
+                mStatusBarKeyguardViewManager,
+                mNotificationStackScrollLayoutController,
+                mLockscreenShadeTransitionController,
+                mNotificationShadeDepthController,
+                mShadeHeaderController,
+                mStatusBarTouchableRegionManager,
+                mKeyguardStateController,
+                mKeyguardBypassController,
+                mUpdateMonitor,
+                mScrimController,
+                mMediaDataManager,
+                mMediaHierarchyManager,
+                mAmbientState,
+                mRecordingController,
+                mFalsingManager,
+                new FalsingCollectorFake(),
+                mAccessibilityManager,
+                mLockscreenGestureLogger,
+                mMetricsLogger,
+                mFeatureFlags,
+                mInteractionJankMonitor,
+                mShadeLog
+        );
+    }
+
+    @After
+    public void tearDown() {
+        mNotificationPanelViewController.mBottomAreaShadeAlphaAnimator.cancel();
+        mNotificationPanelViewController.cancelHeightAnimator();
+        mMainHandler.removeCallbacksAndMessages(null);
+    }
+
+    protected void setBottomPadding(int stackBottom, int lockIconPadding, int indicationPadding,
+            int ambientPadding) {
+
+        when(mNotificationStackScrollLayoutController.getTop()).thenReturn(0);
+        when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(stackBottom);
+        when(mNotificationStackScrollLayoutController.getBottom()).thenReturn(stackBottom);
+        when(mLockIconViewController.getTop()).thenReturn((float) (stackBottom - lockIconPadding));
+
+        when(mResources.getDimensionPixelSize(R.dimen.keyguard_indication_bottom_padding))
+                .thenReturn(indicationPadding);
+        mNotificationPanelViewController.loadDimens();
+
+        mNotificationPanelViewController.setAmbientIndicationTop(
+                /* ambientIndicationTop= */ stackBottom - ambientPadding,
+                /* ambientTextVisible= */ true);
+    }
+
+    protected void triggerPositionClockAndNotifications() {
+        mNotificationPanelViewController.onQsSetExpansionHeightCalled(false);
+    }
+
+    protected FalsingManager.FalsingTapListener getFalsingTapListener() {
+        for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) {
+            listener.onViewAttachedToWindow(mView);
+        }
+        assertThat(mFalsingManager.getTapListeners().size()).isEqualTo(1);
+        return mFalsingManager.getTapListeners().get(0);
+    }
+
+    protected void givenViewAttached() {
+        for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) {
+            listener.onViewAttachedToWindow(mView);
+        }
+    }
+
+    protected ConstraintSet.Layout getConstraintSetLayout(@IdRes int id) {
+        ConstraintSet constraintSet = new ConstraintSet();
+        constraintSet.clone(mNotificationContainerParent);
+        return constraintSet.getConstraint(id).layout;
+    }
+
+    protected void enableSplitShade(boolean enabled) {
+        when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(enabled);
+        mNotificationPanelViewController.updateResources();
+    }
+
+    protected void updateMultiUserSetting(boolean enabled) {
+        when(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)).thenReturn(false);
+        when(mUserManager.isUserSwitcherEnabled(false)).thenReturn(enabled);
+        final ArgumentCaptor<ContentObserver> observerCaptor =
+                ArgumentCaptor.forClass(ContentObserver.class);
+        verify(mContentResolver)
+                .registerContentObserver(any(), anyBoolean(), observerCaptor.capture());
+        observerCaptor.getValue().onChange(/* selfChange */ false);
+    }
+
+    protected void updateSmallestScreenWidth(int smallestScreenWidthDp) {
+        Configuration configuration = new Configuration();
+        configuration.smallestScreenWidthDp = smallestScreenWidthDp;
+        mConfigurationController.onConfigurationChanged(configuration);
+    }
+
+    protected void onTouchEvent(MotionEvent ev) {
+        mTouchHandler.onTouch(mView, ev);
+    }
+
+    protected void setDozing(boolean dozing, boolean dozingAlwaysOn) {
+        when(mDozeParameters.getAlwaysOn()).thenReturn(dozingAlwaysOn);
+        mNotificationPanelViewController.setDozing(
+                /* dozing= */ dozing,
+                /* animate= */ false
+        );
+    }
+
+    protected void assertKeyguardStatusViewCentered() {
+        mNotificationPanelViewController.updateResources();
+        assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd).isAnyOf(
+                ConstraintSet.PARENT_ID, ConstraintSet.UNSET);
+    }
+
+    protected void assertKeyguardStatusViewNotCentered() {
+        mNotificationPanelViewController.updateResources();
+        assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd).isEqualTo(
+                R.id.qs_edge_guideline);
+    }
+
+    protected void setIsFullWidth(boolean fullWidth) {
+        float nsslWidth = fullWidth ? PANEL_WIDTH : PANEL_WIDTH / 2f;
+        when(mNotificationStackScrollLayoutController.getWidth()).thenReturn(nsslWidth);
+        triggerLayoutChange();
+    }
+
+    protected void triggerLayoutChange() {
+        mLayoutChangeListener.onLayoutChange(
+                mView,
+                /* left= */ 0,
+                /* top= */ 0,
+                /* right= */ 0,
+                /* bottom= */ 0,
+                /* oldLeft= */ 0,
+                /* oldTop= */ 0,
+                /* oldRight= */ 0,
+                /* oldBottom= */ 0
+        );
+    }
+
+    protected CoroutineDispatcher getMainDispatcher() {
+        return mMainDispatcher;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 8f6653f..abcde3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.shade;
 
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-
 import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
@@ -31,592 +29,53 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.annotation.IdRes;
-import android.content.ContentResolver;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.UserManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.util.DisplayMetrics;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.ViewPropertyAnimator;
-import android.view.ViewStub;
-import android.view.ViewTreeObserver;
-import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.constraintlayout.widget.ConstraintSet;
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.internal.util.CollectionUtils;
-import com.android.internal.util.LatencyTracker;
 import com.android.keyguard.FaceAuthApiRequestReason;
-import com.android.keyguard.KeyguardClockSwitch;
-import com.android.keyguard.KeyguardClockSwitchController;
-import com.android.keyguard.KeyguardStatusView;
-import com.android.keyguard.KeyguardStatusViewController;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.LockIconViewController;
-import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
-import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
-import com.android.keyguard.dagger.KeyguardStatusViewComponent;
-import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.classifier.FalsingCollectorFake;
-import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.common.ui.view.LongPressHandlingView;
-import com.android.systemui.doze.DozeLog;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.fragments.FragmentService;
-import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
-import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel;
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel;
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
-import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
-import com.android.systemui.media.controls.ui.KeyguardMediaController;
-import com.android.systemui.media.controls.ui.MediaHierarchyManager;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSFragment;
-import com.android.systemui.screenrecord.RecordingController;
-import com.android.systemui.shade.transition.ShadeTransitionController;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.KeyguardIndicationController;
-import com.android.systemui.statusbar.LockscreenShadeTransitionController;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationShadeDepthController;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.NotificationShelfController;
-import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.QsFrameTranslateController;
-import com.android.systemui.statusbar.StatusBarStateControllerImpl;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.notification.ConversationNotificationManager;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-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.NotificationStackSizeCalculator;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
-import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
-import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
-import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
-import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
-import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
-import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
-import com.android.systemui.statusbar.phone.TapAgainViewController;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
-import com.android.systemui.statusbar.window.StatusBarWindowStateController;
-import com.android.systemui.unfold.SysUIUnfoldComponent;
-import com.android.systemui.util.time.FakeSystemClock;
-import com.android.systemui.util.time.SystemClock;
-import com.android.wm.shell.animation.FlingAnimationUtils;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
 import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.stubbing.Answer;
 
 import java.util.List;
-import java.util.Optional;
-
-import dagger.Lazy;
-import kotlinx.coroutines.CoroutineDispatcher;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class NotificationPanelViewControllerTest extends SysuiTestCase {
-
-    private static final int SPLIT_SHADE_FULL_TRANSITION_DISTANCE = 400;
-    private static final int NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE = 50;
-    private static final int PANEL_WIDTH = 500; // Random value just for the test.
-
-    @Mock private CentralSurfaces mCentralSurfaces;
-    @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout;
-    @Mock private KeyguardBottomAreaView mKeyguardBottomArea;
-    @Mock private KeyguardBottomAreaViewController mKeyguardBottomAreaViewController;
-    @Mock private KeyguardBottomAreaView mQsFrame;
-    @Mock private HeadsUpManagerPhone mHeadsUpManager;
-    @Mock private NotificationShelfController mNotificationShelfController;
-    @Mock private NotificationGutsManager mGutsManager;
-    @Mock private KeyguardStatusBarView mKeyguardStatusBar;
-    @Mock private KeyguardUserSwitcherView mUserSwitcherView;
-    @Mock private ViewStub mUserSwitcherStubView;
-    @Mock private HeadsUpTouchHelper.Callback mHeadsUpCallback;
-    @Mock private KeyguardUpdateMonitor mUpdateMonitor;
-    @Mock private KeyguardBypassController mKeyguardBypassController;
-    @Mock private DozeParameters mDozeParameters;
-    @Mock private ScreenOffAnimationController mScreenOffAnimationController;
-    @Mock private NotificationPanelView mView;
-    @Mock private LayoutInflater mLayoutInflater;
-    @Mock private FeatureFlags mFeatureFlags;
-    @Mock private DynamicPrivacyController mDynamicPrivacyController;
-    @Mock private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
-    @Mock private KeyguardStateController mKeyguardStateController;
-    @Mock private DozeLog mDozeLog;
-    @Mock private ShadeLogger mShadeLog;
-    @Mock private ShadeHeightLogger mShadeHeightLogger;
-    @Mock private CommandQueue mCommandQueue;
-    @Mock private VibratorHelper mVibratorHelper;
-    @Mock private LatencyTracker mLatencyTracker;
-    @Mock private PowerManager mPowerManager;
-    @Mock private AccessibilityManager mAccessibilityManager;
-    @Mock private MetricsLogger mMetricsLogger;
-    @Mock private Resources mResources;
-    @Mock private Configuration mConfiguration;
-    @Mock private KeyguardClockSwitch mKeyguardClockSwitch;
-    @Mock private MediaHierarchyManager mMediaHierarchyManager;
-    @Mock private ConversationNotificationManager mConversationNotificationManager;
-    @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    @Mock private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
-    @Mock private KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory;
-    @Mock private KeyguardQsUserSwitchComponent mKeyguardQsUserSwitchComponent;
-    @Mock private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
-    @Mock private KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory;
-    @Mock private KeyguardUserSwitcherComponent mKeyguardUserSwitcherComponent;
-    @Mock private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
-    @Mock private KeyguardStatusViewComponent mKeyguardStatusViewComponent;
-    @Mock private KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
-    @Mock private KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent;
-    @Mock private KeyguardClockSwitchController mKeyguardClockSwitchController;
-    @Mock private KeyguardStatusViewController mKeyguardStatusViewController;
-    @Mock private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
-    @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
-    @Mock private NotificationShadeDepthController mNotificationShadeDepthController;
-    @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
-    @Mock private AuthController mAuthController;
-    @Mock private ScrimController mScrimController;
-    @Mock private MediaDataManager mMediaDataManager;
-    @Mock private AmbientState mAmbientState;
-    @Mock private UserManager mUserManager;
-    @Mock private UiEventLogger mUiEventLogger;
-    @Mock private LockIconViewController mLockIconViewController;
-    @Mock private KeyguardMediaController mKeyguardMediaController;
-    @Mock private NavigationModeController mNavigationModeController;
-    @Mock private NavigationBarController mNavigationBarController;
-    @Mock private QuickSettingsController mQsController;
-    @Mock private ShadeHeaderController mShadeHeaderController;
-    @Mock private ContentResolver mContentResolver;
-    @Mock private TapAgainViewController mTapAgainViewController;
-    @Mock private KeyguardIndicationController mKeyguardIndicationController;
-    @Mock private FragmentService mFragmentService;
-    @Mock private FragmentHostManager mFragmentHostManager;
-    @Mock private NotificationRemoteInputManager mNotificationRemoteInputManager;
-    @Mock private RecordingController mRecordingController;
-    @Mock private LockscreenGestureLogger mLockscreenGestureLogger;
-    @Mock private DumpManager mDumpManager;
-    @Mock private InteractionJankMonitor mInteractionJankMonitor;
-    @Mock private NotificationsQSContainerController mNotificationsQSContainerController;
-    @Mock private QsFrameTranslateController mQsFrameTranslateController;
-    @Mock private StatusBarWindowStateController mStatusBarWindowStateController;
-    @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
-    @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
-    @Mock private SysUiState mSysUiState;
-    @Mock private NotificationListContainer mNotificationListContainer;
-    @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
-    @Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
-    @Mock private ShadeTransitionController mShadeTransitionController;
-    @Mock private QS mQs;
-    @Mock private QSFragment mQSFragment;
-    @Mock private ViewGroup mQsHeader;
-    @Mock private ViewParent mViewParent;
-    @Mock private ViewTreeObserver mViewTreeObserver;
-    @Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
-    @Mock private KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
-    @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
-    @Mock private DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
-    @Mock private OccludedToLockscreenTransitionViewModel mOccludedToLockscreenTransitionViewModel;
-    @Mock private LockscreenToDreamingTransitionViewModel mLockscreenToDreamingTransitionViewModel;
-    @Mock private LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel;
-    @Mock private GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel;
-
-    @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
-    @Mock private KeyguardInteractor mKeyguardInteractor;
-    @Mock private KeyguardLongPressViewModel mKeyuardLongPressViewModel;
-    @Mock private CoroutineDispatcher mMainDispatcher;
-    @Mock private MotionEvent mDownMotionEvent;
-    @Captor
-    private ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener>
-            mEmptySpaceClickListenerCaptor;
-
-    private NotificationPanelViewController.TouchHandler mTouchHandler;
-    private ConfigurationController mConfigurationController;
-    private SysuiStatusBarStateController mStatusBarStateController;
-    private NotificationPanelViewController mNotificationPanelViewController;
-    private View.AccessibilityDelegate mAccessibilityDelegate;
-    private NotificationsQuickSettingsContainer mNotificationContainerParent;
-    private List<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners;
-    private Handler mMainHandler;
-    private View.OnLayoutChangeListener mLayoutChangeListener;
-
-    private final FalsingManagerFake mFalsingManager = new FalsingManagerFake();
-    private final Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
-    private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
-    private final ShadeExpansionStateManager mShadeExpansionStateManager =
-            new ShadeExpansionStateManager();
-
-    private QuickSettingsController mQuickSettingsController;
-    @Mock private Lazy<NotificationPanelViewController> mNotificationPanelViewControllerLazy;
-
-    private FragmentHostManager.FragmentListener mFragmentListener;
+public class NotificationPanelViewControllerTest extends NotificationPanelViewControllerBaseTest {
 
     @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        SystemClock systemClock = new FakeSystemClock();
-        mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager,
-                mInteractionJankMonitor, mShadeExpansionStateManager);
-
-        KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
-        keyguardStatusView.setId(R.id.keyguard_status_view);
+    public void before() {
         DejankUtils.setImmediate(true);
-
-        when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
-        when(mHeadsUpCallback.getContext()).thenReturn(mContext);
-        when(mView.getResources()).thenReturn(mResources);
-        when(mView.getWidth()).thenReturn(PANEL_WIDTH);
-        when(mResources.getConfiguration()).thenReturn(mConfiguration);
-        mConfiguration.orientation = ORIENTATION_PORTRAIT;
-        when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
-        mDisplayMetrics.density = 100;
-        when(mResources.getBoolean(R.bool.config_enableNotificationShadeDrag)).thenReturn(true);
-        when(mResources.getDimensionPixelSize(R.dimen.notifications_top_padding_split_shade))
-                .thenReturn(NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE);
-        when(mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal))
-                .thenReturn(10);
-        when(mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance))
-                .thenReturn(SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
-        when(mView.getContext()).thenReturn(getContext());
-        when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar);
-        when(mView.findViewById(R.id.keyguard_user_switcher_view)).thenReturn(mUserSwitcherView);
-        when(mView.findViewById(R.id.keyguard_user_switcher_stub)).thenReturn(
-                mUserSwitcherStubView);
-        when(mView.findViewById(R.id.keyguard_clock_container)).thenReturn(mKeyguardClockSwitch);
-        when(mView.findViewById(R.id.notification_stack_scroller))
-                .thenReturn(mNotificationStackScrollLayout);
-        when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(1000);
-        when(mNotificationStackScrollLayoutController.getHeadsUpCallback())
-                .thenReturn(mHeadsUpCallback);
-        when(mKeyguardBottomAreaViewController.getView()).thenReturn(mKeyguardBottomArea);
-        when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea);
-        when(mKeyguardBottomArea.animate()).thenReturn(mock(ViewPropertyAnimator.class));
-        when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
-        when(mView.findViewById(R.id.keyguard_status_view))
-                .thenReturn(mock(KeyguardStatusView.class));
-        mNotificationContainerParent = new NotificationsQuickSettingsContainer(getContext(), null);
-        mNotificationContainerParent.addView(keyguardStatusView);
-        mNotificationContainerParent.onFinishInflate();
-        when(mView.findViewById(R.id.notification_container_parent))
-                .thenReturn(mNotificationContainerParent);
-        when(mFragmentService.getFragmentHostManager(mView)).thenReturn(mFragmentHostManager);
-        FlingAnimationUtils.Builder flingAnimationUtilsBuilder = new FlingAnimationUtils.Builder(
-                mDisplayMetrics);
-        when(mKeyguardQsUserSwitchComponentFactory.build(any()))
-                .thenReturn(mKeyguardQsUserSwitchComponent);
-        when(mKeyguardQsUserSwitchComponent.getKeyguardQsUserSwitchController())
-                .thenReturn(mKeyguardQsUserSwitchController);
-        when(mKeyguardUserSwitcherComponentFactory.build(any()))
-                .thenReturn(mKeyguardUserSwitcherComponent);
-        when(mKeyguardUserSwitcherComponent.getKeyguardUserSwitcherController())
-                .thenReturn(mKeyguardUserSwitcherController);
-        when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(true);
-        when(mQs.getView()).thenReturn(mView);
-        when(mQSFragment.getView()).thenReturn(mView);
-        doAnswer(invocation -> {
-            mFragmentListener = invocation.getArgument(1);
-            return null;
-        }).when(mFragmentHostManager).addTagListener(eq(QS.TAG), any());
-        doAnswer((Answer<Void>) invocation -> {
-            mTouchHandler = invocation.getArgument(0);
-            return null;
-        }).when(mView).setOnTouchListener(any(NotificationPanelViewController.TouchHandler.class));
-
-        NotificationWakeUpCoordinator coordinator =
-                new NotificationWakeUpCoordinator(
-                        mDumpManager,
-                        mock(HeadsUpManagerPhone.class),
-                        new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager,
-                                mInteractionJankMonitor, mShadeExpansionStateManager),
-                        mKeyguardBypassController,
-                        mDozeParameters,
-                        mScreenOffAnimationController,
-                        mock(NotificationWakeUpCoordinatorLogger.class));
-        mConfigurationController = new ConfigurationControllerImpl(mContext);
-        PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
-                mContext,
-                coordinator,
-                mKeyguardBypassController, mHeadsUpManager,
-                mock(NotificationRoundnessManager.class),
-                mConfigurationController,
-                mStatusBarStateController,
-                mFalsingManager,
-                mShadeExpansionStateManager,
-                mLockscreenShadeTransitionController,
-                new FalsingCollectorFake(),
-                mDumpManager);
-        when(mKeyguardStatusViewComponentFactory.build(any()))
-                .thenReturn(mKeyguardStatusViewComponent);
-        when(mKeyguardStatusViewComponent.getKeyguardClockSwitchController())
-                .thenReturn(mKeyguardClockSwitchController);
-        when(mKeyguardStatusViewComponent.getKeyguardStatusViewController())
-                .thenReturn(mKeyguardStatusViewController);
-        when(mKeyguardStatusBarViewComponentFactory.build(any(), any()))
-                .thenReturn(mKeyguardStatusBarViewComponent);
-        when(mKeyguardStatusBarViewComponent.getKeyguardStatusBarViewController())
-                .thenReturn(mKeyguardStatusBarViewController);
-        when(mLayoutInflater.inflate(eq(R.layout.keyguard_status_view), any(), anyBoolean()))
-                .thenReturn(keyguardStatusView);
-        when(mLayoutInflater.inflate(eq(R.layout.keyguard_user_switcher), any(), anyBoolean()))
-                .thenReturn(mUserSwitcherView);
-        when(mLayoutInflater.inflate(eq(R.layout.keyguard_bottom_area), any(), anyBoolean()))
-                .thenReturn(mKeyguardBottomArea);
-        when(mNotificationRemoteInputManager.isRemoteInputActive())
-                .thenReturn(false);
-        when(mInteractionJankMonitor.begin(any(), anyInt()))
-                .thenReturn(true);
-        when(mInteractionJankMonitor.end(anyInt()))
-                .thenReturn(true);
-        doAnswer(invocation -> {
-            ((Runnable) invocation.getArgument(0)).run();
-            return null;
-        }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any());
-        doAnswer(invocation -> {
-            mLayoutChangeListener = invocation.getArgument(0);
-            return null;
-        }).when(mView).addOnLayoutChangeListener(any());
-
-        when(mView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
-        when(mView.getParent()).thenReturn(mViewParent);
-        when(mQs.getHeader()).thenReturn(mQsHeader);
-        when(mDownMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN);
-        when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
-
-        mMainHandler = new Handler(Looper.getMainLooper());
-
-        when(mView.requireViewById(R.id.keyguard_long_press))
-                .thenReturn(mock(LongPressHandlingView.class));
-
-        mNotificationPanelViewController = new NotificationPanelViewController(
-                mView,
-                mMainHandler,
-                mLayoutInflater,
-                mFeatureFlags,
-                coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController,
-                mFalsingManager, new FalsingCollectorFake(),
-                mKeyguardStateController,
-                mStatusBarStateController,
-                mStatusBarWindowStateController,
-                mNotificationShadeWindowController,
-                mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper,
-                mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
-                mMetricsLogger,
-                mShadeLog,
-                mShadeHeightLogger,
-                mConfigurationController,
-                () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
-                mConversationNotificationManager, mMediaHierarchyManager,
-                mStatusBarKeyguardViewManager,
-                mGutsManager,
-                mNotificationsQSContainerController,
-                mNotificationStackScrollLayoutController,
-                mKeyguardStatusViewComponentFactory,
-                mKeyguardQsUserSwitchComponentFactory,
-                mKeyguardUserSwitcherComponentFactory,
-                mKeyguardStatusBarViewComponentFactory,
-                mLockscreenShadeTransitionController,
-                mAuthController,
-                mScrimController,
-                mUserManager,
-                mMediaDataManager,
-                mNotificationShadeDepthController,
-                mAmbientState,
-                mLockIconViewController,
-                mKeyguardMediaController,
-                mTapAgainViewController,
-                mNavigationModeController,
-                mNavigationBarController,
-                mQsController,
-                mFragmentService,
-                mContentResolver,
-                mRecordingController,
-                mShadeHeaderController,
-                mScreenOffAnimationController,
-                mLockscreenGestureLogger,
-                mShadeExpansionStateManager,
-                mNotificationRemoteInputManager,
-                mSysUIUnfoldComponent,
-                mSysUiState,
-                () -> mKeyguardBottomAreaViewController,
-                mKeyguardUnlockAnimationController,
-                mKeyguardIndicationController,
-                mNotificationListContainer,
-                mNotificationStackSizeCalculator,
-                mUnlockedScreenOffAnimationController,
-                mShadeTransitionController,
-                systemClock,
-                mKeyguardBottomAreaViewModel,
-                mKeyguardBottomAreaInteractor,
-                mAlternateBouncerInteractor,
-                mDreamingToLockscreenTransitionViewModel,
-                mOccludedToLockscreenTransitionViewModel,
-                mLockscreenToDreamingTransitionViewModel,
-                mGoneToDreamingTransitionViewModel,
-                mLockscreenToOccludedTransitionViewModel,
-                mMainDispatcher,
-                mKeyguardTransitionInteractor,
-                mDumpManager,
-                mKeyuardLongPressViewModel,
-                mKeyguardInteractor);
-        mNotificationPanelViewController.initDependencies(
-                mCentralSurfaces,
-                null,
-                () -> {},
-                mNotificationShelfController);
-        mNotificationPanelViewController.setTrackingStartedListener(() -> {});
-        mNotificationPanelViewController.setOpenCloseListener(
-                new NotificationPanelViewController.OpenCloseListener() {
-                    @Override
-                    public void onClosingFinished() {}
-
-                    @Override
-                    public void onOpenStarted() {}
-                });
-        mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
-        ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor =
-                ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
-        verify(mView, atLeast(1)).addOnAttachStateChangeListener(
-                onAttachStateChangeListenerArgumentCaptor.capture());
-        mOnAttachStateChangeListeners = onAttachStateChangeListenerArgumentCaptor.getAllValues();
-
-        ArgumentCaptor<View.AccessibilityDelegate> accessibilityDelegateArgumentCaptor =
-                ArgumentCaptor.forClass(View.AccessibilityDelegate.class);
-        verify(mView).setAccessibilityDelegate(accessibilityDelegateArgumentCaptor.capture());
-        mAccessibilityDelegate = accessibilityDelegateArgumentCaptor.getValue();
-        mNotificationPanelViewController.getStatusBarStateController()
-                .addCallback(mNotificationPanelViewController.getStatusBarStateListener());
-        mNotificationPanelViewController
-                .setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class));
-        verify(mNotificationStackScrollLayoutController)
-                .setOnEmptySpaceClickListener(mEmptySpaceClickListenerCaptor.capture());
-        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
-        reset(mKeyguardStatusViewController);
-
-        when(mNotificationPanelViewControllerLazy.get())
-                .thenReturn(mNotificationPanelViewController);
-        mQuickSettingsController = new QuickSettingsController(
-                mNotificationPanelViewControllerLazy,
-                mView,
-                mQsFrameTranslateController,
-                mShadeTransitionController,
-                expansionHandler,
-                mNotificationRemoteInputManager,
-                mShadeExpansionStateManager,
-                mStatusBarKeyguardViewManager,
-                mNotificationStackScrollLayoutController,
-                mLockscreenShadeTransitionController,
-                mNotificationShadeDepthController,
-                mShadeHeaderController,
-                mStatusBarTouchableRegionManager,
-                mKeyguardStateController,
-                mKeyguardBypassController,
-                mUpdateMonitor,
-                mScrimController,
-                mMediaDataManager,
-                mMediaHierarchyManager,
-                mAmbientState,
-                mRecordingController,
-                mFalsingManager,
-                new FalsingCollectorFake(),
-                mAccessibilityManager,
-                mLockscreenGestureLogger,
-                mMetricsLogger,
-                mFeatureFlags,
-                mInteractionJankMonitor,
-                mShadeLog
-        );
-    }
-
-    @After
-    public void tearDown() {
-        mNotificationPanelViewController.cancelHeightAnimator();
-        mMainHandler.removeCallbacksAndMessages(null);
     }
 
     @Test
@@ -671,23 +130,6 @@
                 .isNotEqualTo(-1);
     }
 
-    private void setBottomPadding(int stackBottom, int lockIconPadding, int indicationPadding,
-            int ambientPadding) {
-
-        when(mNotificationStackScrollLayoutController.getTop()).thenReturn(0);
-        when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(stackBottom);
-        when(mNotificationStackScrollLayoutController.getBottom()).thenReturn(stackBottom);
-        when(mLockIconViewController.getTop()).thenReturn((float) (stackBottom - lockIconPadding));
-
-        when(mResources.getDimensionPixelSize(R.dimen.keyguard_indication_bottom_padding))
-                .thenReturn(indicationPadding);
-        mNotificationPanelViewController.loadDimens();
-
-        mNotificationPanelViewController.setAmbientIndicationTop(
-                /* ambientIndicationTop= */ stackBottom - ambientPadding,
-                /* ambientTextVisible= */ true);
-    }
-
     @Test
     @Ignore("b/261472011 - Test appears inconsistent across environments")
     public void getVerticalSpaceForLockscreenNotifications_useLockIconBottomPadding_returnsSpaceAvailable() {
@@ -992,68 +434,6 @@
     }
 
     @Test
-    public void testDisableUserSwitcherAfterEnabling_returnsViewStubToTheViewHierarchy() {
-        givenViewAttached();
-        when(mResources.getBoolean(
-                com.android.internal.R.bool.config_keyguardUserSwitcher)).thenReturn(true);
-        updateMultiUserSetting(true);
-        clearInvocations(mView);
-
-        updateMultiUserSetting(false);
-
-        ArgumentCaptor<View> captor = ArgumentCaptor.forClass(View.class);
-        verify(mView, atLeastOnce()).addView(captor.capture(), anyInt());
-        final View userSwitcherStub = CollectionUtils.find(captor.getAllValues(),
-                view -> view.getId() == R.id.keyguard_user_switcher_stub);
-        assertThat(userSwitcherStub).isNotNull();
-        assertThat(userSwitcherStub).isInstanceOf(ViewStub.class);
-    }
-
-    @Test
-    public void testChangeSmallestScreenWidthAndUserSwitchEnabled_inflatesUserSwitchView() {
-        givenViewAttached();
-        when(mView.findViewById(R.id.keyguard_user_switcher_view)).thenReturn(null);
-        updateSmallestScreenWidth(300);
-        when(mResources.getBoolean(
-                com.android.internal.R.bool.config_keyguardUserSwitcher)).thenReturn(true);
-        when(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)).thenReturn(false);
-        when(mUserManager.isUserSwitcherEnabled(false)).thenReturn(true);
-
-        updateSmallestScreenWidth(800);
-
-        verify(mUserSwitcherStubView).inflate();
-    }
-
-    @Test
-    public void testFinishInflate_userSwitcherDisabled_doNotInflateUserSwitchView_initClock() {
-        givenViewAttached();
-        when(mResources.getBoolean(
-                com.android.internal.R.bool.config_keyguardUserSwitcher)).thenReturn(true);
-        when(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)).thenReturn(false);
-        when(mUserManager.isUserSwitcherEnabled(false /* showEvenIfNotActionable */))
-                .thenReturn(false);
-
-        mNotificationPanelViewController.onFinishInflate();
-
-        verify(mUserSwitcherStubView, never()).inflate();
-        verify(mKeyguardStatusViewController, times(3)).displayClock(LARGE, /* animate */ true);
-    }
-
-    @Test
-    public void testReInflateViews_userSwitcherDisabled_doNotInflateUserSwitchView() {
-        givenViewAttached();
-        when(mResources.getBoolean(
-                com.android.internal.R.bool.config_keyguardUserSwitcher)).thenReturn(true);
-        when(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)).thenReturn(false);
-        when(mUserManager.isUserSwitcherEnabled(false /* showEvenIfNotActionable */))
-                .thenReturn(false);
-
-        mNotificationPanelViewController.reInflateViews();
-
-        verify(mUserSwitcherStubView, never()).inflate();
-    }
-
-    @Test
     public void testCanCollapsePanelOnTouch_trueForKeyGuard() {
         mStatusBarStateController.setState(KEYGUARD);
 
@@ -1129,26 +509,6 @@
     }
 
     @Test
-    public void testDoubleTapRequired_Keyguard() {
-        FalsingManager.FalsingTapListener listener = getFalsingTapListener();
-        mStatusBarStateController.setState(KEYGUARD);
-
-        listener.onAdditionalTapRequired();
-
-        verify(mKeyguardIndicationController).showTransientIndication(anyInt());
-    }
-
-    @Test
-    public void testDoubleTapRequired_ShadeLocked() {
-        FalsingManager.FalsingTapListener listener = getFalsingTapListener();
-        mStatusBarStateController.setState(SHADE_LOCKED);
-
-        listener.onAdditionalTapRequired();
-
-        verify(mTapAgainViewController).show();
-    }
-
-    @Test
     public void testRotatingToSplitShadeWithQsExpanded_transitionsToShadeLocked() {
         mStatusBarStateController.setState(KEYGUARD);
         when(mQsController.getExpanded()).thenReturn(true);
@@ -1423,19 +783,6 @@
     }
 
     @Test
-    public void testOnAttachRefreshStatusBarState() {
-        mStatusBarStateController.setState(KEYGUARD);
-        when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(false);
-        for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) {
-            listener.onViewAttachedToWindow(mView);
-        }
-        verify(mKeyguardStatusViewController).setKeyguardStatusViewVisibility(
-                KEYGUARD/*statusBarState*/,
-                false/*keyguardFadingAway*/,
-                false/*goingToFullShade*/, SHADE/*oldStatusBarState*/);
-    }
-
-    @Test
     public void getMaxPanelTransitionDistance_expanding_inSplitShade_returnsSplitShadeFullTransitionDistance() {
         enableSplitShade(true);
         mNotificationPanelViewController.expandWithQs();
@@ -1635,98 +982,4 @@
         mStatusBarStateController.setState(SHADE_LOCKED);
         assertThat(mNotificationPanelViewController.isShadeFullyOpen()).isTrue();
     }
-
-    private static MotionEvent createMotionEvent(int x, int y, int action) {
-        return MotionEvent.obtain(
-                /* downTime= */ 0, /* eventTime= */ 0, action, x, y, /* metaState= */ 0);
-    }
-
-    private void triggerPositionClockAndNotifications() {
-        mNotificationPanelViewController.onQsSetExpansionHeightCalled(false);
-    }
-
-    private FalsingManager.FalsingTapListener getFalsingTapListener() {
-        for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) {
-            listener.onViewAttachedToWindow(mView);
-        }
-        assertThat(mFalsingManager.getTapListeners().size()).isEqualTo(1);
-        return mFalsingManager.getTapListeners().get(0);
-    }
-
-    private void givenViewAttached() {
-        for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) {
-            listener.onViewAttachedToWindow(mView);
-        }
-    }
-
-    private ConstraintSet.Layout getConstraintSetLayout(@IdRes int id) {
-        ConstraintSet constraintSet = new ConstraintSet();
-        constraintSet.clone(mNotificationContainerParent);
-        return constraintSet.getConstraint(id).layout;
-    }
-
-    private void enableSplitShade(boolean enabled) {
-        when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(enabled);
-        mNotificationPanelViewController.updateResources();
-    }
-
-    private void updateMultiUserSetting(boolean enabled) {
-        when(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)).thenReturn(false);
-        when(mUserManager.isUserSwitcherEnabled(false)).thenReturn(enabled);
-        final ArgumentCaptor<ContentObserver> observerCaptor =
-                ArgumentCaptor.forClass(ContentObserver.class);
-        verify(mContentResolver)
-                .registerContentObserver(any(), anyBoolean(), observerCaptor.capture());
-        observerCaptor.getValue().onChange(/* selfChange */ false);
-    }
-
-    private void updateSmallestScreenWidth(int smallestScreenWidthDp) {
-        Configuration configuration = new Configuration();
-        configuration.smallestScreenWidthDp = smallestScreenWidthDp;
-        mConfigurationController.onConfigurationChanged(configuration);
-    }
-
-    private void onTouchEvent(MotionEvent ev) {
-        mTouchHandler.onTouch(mView, ev);
-    }
-
-    private void setDozing(boolean dozing, boolean dozingAlwaysOn) {
-        when(mDozeParameters.getAlwaysOn()).thenReturn(dozingAlwaysOn);
-        mNotificationPanelViewController.setDozing(
-                /* dozing= */ dozing,
-                /* animate= */ false
-        );
-    }
-
-    private void assertKeyguardStatusViewCentered() {
-        mNotificationPanelViewController.updateResources();
-        assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd).isAnyOf(
-                ConstraintSet.PARENT_ID, ConstraintSet.UNSET);
-    }
-
-    private void assertKeyguardStatusViewNotCentered() {
-        mNotificationPanelViewController.updateResources();
-        assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd).isEqualTo(
-                R.id.qs_edge_guideline);
-    }
-
-    private void setIsFullWidth(boolean fullWidth) {
-        float nsslWidth = fullWidth ? PANEL_WIDTH : PANEL_WIDTH / 2f;
-        when(mNotificationStackScrollLayoutController.getWidth()).thenReturn(nsslWidth);
-        triggerLayoutChange();
-    }
-
-    private void triggerLayoutChange() {
-        mLayoutChangeListener.onLayoutChange(
-                mView,
-                /* left= */ 0,
-                /* top= */ 0,
-                /* right= */ 0,
-                /* bottom= */ 0,
-                /* oldLeft= */ 0,
-                /* oldTop= */ 0,
-                /* oldRight= */ 0,
-                /* oldBottom= */ 0
-        );
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
new file mode 100644
index 0000000..0c046e9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
@@ -0,0 +1,179 @@
+/*
+ * 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.shade
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import android.view.ViewStub
+import androidx.test.filters.SmallTest
+import com.android.internal.util.CollectionUtils
+import com.android.keyguard.KeyguardClockSwitch.LARGE
+import com.android.systemui.R
+import com.android.systemui.statusbar.StatusBarState.KEYGUARD
+import com.android.systemui.statusbar.StatusBarState.SHADE
+import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Captor
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class NotificationPanelViewControllerWithCoroutinesTest :
+    NotificationPanelViewControllerBaseTest() {
+
+    @Captor private lateinit var viewCaptor: ArgumentCaptor<View>
+
+    override fun getMainDispatcher() = Dispatchers.Main.immediate
+
+    @Test
+    fun testDisableUserSwitcherAfterEnabling_returnsViewStubToTheViewHierarchy() = runTest {
+        launch(Dispatchers.Main.immediate) { givenViewAttached() }
+        advanceUntilIdle()
+
+        whenever(mResources.getBoolean(com.android.internal.R.bool.config_keyguardUserSwitcher))
+            .thenReturn(true)
+        updateMultiUserSetting(true)
+        clearInvocations(mView)
+
+        updateMultiUserSetting(false)
+
+        verify(mView, atLeastOnce()).addView(viewCaptor.capture(), anyInt())
+        val userSwitcherStub =
+            CollectionUtils.find(
+                viewCaptor.getAllValues(),
+                { view -> view.getId() == R.id.keyguard_user_switcher_stub }
+            )
+        assertThat(userSwitcherStub).isNotNull()
+        assertThat(userSwitcherStub).isInstanceOf(ViewStub::class.java)
+    }
+
+    @Test
+    fun testChangeSmallestScreenWidthAndUserSwitchEnabled_inflatesUserSwitchView() = runTest {
+        launch(Dispatchers.Main.immediate) { givenViewAttached() }
+        advanceUntilIdle()
+
+        whenever(mView.findViewById<View>(R.id.keyguard_user_switcher_view)).thenReturn(null)
+        updateSmallestScreenWidth(300)
+        whenever(mResources.getBoolean(com.android.internal.R.bool.config_keyguardUserSwitcher))
+            .thenReturn(true)
+        whenever(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user))
+            .thenReturn(false)
+        whenever(mUserManager.isUserSwitcherEnabled(false)).thenReturn(true)
+
+        updateSmallestScreenWidth(800)
+
+        verify(mUserSwitcherStubView).inflate()
+    }
+
+    @Test
+    fun testFinishInflate_userSwitcherDisabled_doNotInflateUserSwitchView_initClock() = runTest {
+        launch(Dispatchers.Main.immediate) { givenViewAttached() }
+        advanceUntilIdle()
+
+        whenever(mResources.getBoolean(com.android.internal.R.bool.config_keyguardUserSwitcher))
+            .thenReturn(true)
+        whenever(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user))
+            .thenReturn(false)
+        whenever(mUserManager.isUserSwitcherEnabled(false /* showEvenIfNotActionable */))
+            .thenReturn(false)
+
+        mNotificationPanelViewController.onFinishInflate()
+
+        verify(mUserSwitcherStubView, never()).inflate()
+        verify(mKeyguardStatusViewController, times(3)).displayClock(LARGE, /* animate */ true)
+
+        coroutineContext.cancelChildren()
+    }
+
+    @Test
+    fun testReInflateViews_userSwitcherDisabled_doNotInflateUserSwitchView() = runTest {
+        launch(Dispatchers.Main.immediate) { givenViewAttached() }
+        advanceUntilIdle()
+
+        whenever(mResources.getBoolean(com.android.internal.R.bool.config_keyguardUserSwitcher))
+            .thenReturn(true)
+        whenever(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user))
+            .thenReturn(false)
+        whenever(mUserManager.isUserSwitcherEnabled(false /* showEvenIfNotActionable */))
+            .thenReturn(false)
+
+        mNotificationPanelViewController.reInflateViews()
+
+        verify(mUserSwitcherStubView, never()).inflate()
+
+        coroutineContext.cancelChildren()
+    }
+
+    @Test
+    fun testDoubleTapRequired_Keyguard() = runTest {
+        launch(Dispatchers.Main.immediate) {
+            val listener = getFalsingTapListener()
+            mStatusBarStateController.setState(KEYGUARD)
+
+            listener.onAdditionalTapRequired()
+
+            verify(mKeyguardIndicationController).showTransientIndication(anyInt())
+        }
+        advanceUntilIdle()
+    }
+
+    @Test
+    fun testDoubleTapRequired_ShadeLocked() = runTest {
+        launch(Dispatchers.Main.immediate) {
+            val listener = getFalsingTapListener()
+            mStatusBarStateController.setState(SHADE_LOCKED)
+
+            listener.onAdditionalTapRequired()
+
+            verify(mTapAgainViewController).show()
+        }
+        advanceUntilIdle()
+    }
+
+    @Test
+    fun testOnAttachRefreshStatusBarState() = runTest {
+        launch(Dispatchers.Main.immediate) {
+            mStatusBarStateController.setState(KEYGUARD)
+            whenever(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(false)
+            mOnAttachStateChangeListeners.forEach { it.onViewAttachedToWindow(mView) }
+            verify(mKeyguardStatusViewController)
+                .setKeyguardStatusViewVisibility(
+                    KEYGUARD /*statusBarState*/,
+                    false /*keyguardFadingAway*/,
+                    false /*goingToFullShade*/,
+                    SHADE /*oldStatusBarState*/
+                )
+        }
+        advanceUntilIdle()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index d229a08..0a401b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -28,10 +28,10 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.dock.DockManager
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -47,6 +47,7 @@
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.emptyFlow
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -81,8 +82,6 @@
     @Mock
     private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
     @Mock
-    private lateinit var featureFlags: FeatureFlags
-    @Mock
     private lateinit var ambientState: AmbientState
     @Mock
     private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
@@ -124,6 +123,8 @@
                 .thenReturn(keyguardBouncerComponent)
         whenever(keyguardBouncerComponent.securityContainerController)
                 .thenReturn(keyguardSecurityContainerController)
+        whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
+                .thenReturn(emptyFlow<TransitionStep>())
         underTest = NotificationShadeWindowViewController(
             lockscreenShadeTransitionController,
             FalsingCollectorFake(),
@@ -143,7 +144,6 @@
             notificationInsetsController,
             ambientState,
             pulsingGestureListener,
-            featureFlags,
             keyguardBouncerViewModel,
             keyguardBouncerComponentFactory,
             alternateBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index 5e9c219..5d71979 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -25,6 +25,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
 import android.os.SystemClock;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -40,7 +42,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.dock.DockManager;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -93,7 +94,6 @@
     @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     @Mock private AmbientState mAmbientState;
     @Mock private PulsingGestureListener mPulsingGestureListener;
-    @Mock private FeatureFlags mFeatureFlags;
     @Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel;
     @Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
     @Mock private KeyguardBouncerComponent mKeyguardBouncerComponent;
@@ -125,6 +125,9 @@
 
         when(mDockManager.isDocked()).thenReturn(false);
 
+        when(mKeyguardTransitionInteractor.getLockscreenToDreamingTransition())
+                .thenReturn(emptyFlow());
+
         mController = new NotificationShadeWindowViewController(
                 mLockscreenShadeTransitionController,
                 new FalsingCollectorFake(),
@@ -144,7 +147,6 @@
                 mNotificationInsetsController,
                 mAmbientState,
                 mPulsingGestureListener,
-                mFeatureFlags,
                 mKeyguardBouncerViewModel,
                 mKeyguardBouncerComponentFactory,
                 mAlternateBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeRepoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeRepoTest.kt
deleted file mode 100644
index a6a9e51..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeRepoTest.kt
+++ /dev/null
@@ -1,210 +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.
- */
-
-package com.android.systemui.statusbar.notification.fsi
-
-import android.R
-import android.app.Notification
-import android.app.NotificationManager
-import android.app.PendingIntent
-import android.content.pm.ApplicationInfo
-import android.content.pm.PackageManager
-import android.graphics.drawable.Drawable
-import android.os.UserHandle
-import android.service.dreams.IDreamManager
-import android.service.notification.StatusBarNotification
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider
-import com.android.systemui.statusbar.phone.CentralSurfaces
-import java.util.concurrent.Executor
-import junit.framework.Assert.assertEquals
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyString
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.never
-import org.mockito.Mockito.times
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper(setAsMainLooper = true)
-class FsiChromeRepoTest : SysuiTestCase() {
-
-    @Mock lateinit var centralSurfaces: CentralSurfaces
-    @Mock lateinit var fsiChromeRepo: FsiChromeRepo
-    @Mock lateinit var packageManager: PackageManager
-
-    var keyguardRepo = FakeKeyguardRepository()
-    @Mock private lateinit var applicationInfo: ApplicationInfo
-
-    @Mock lateinit var launchFullScreenIntentProvider: LaunchFullScreenIntentProvider
-    var featureFlags = FakeFeatureFlags()
-    @Mock lateinit var dreamManager: IDreamManager
-
-    // Execute all foreground & background requests immediately
-    private val uiBgExecutor = Executor { r -> r.run() }
-
-    private val appName: String = "appName"
-    private val appIcon: Drawable = context.getDrawable(com.android.systemui.R.drawable.ic_android)
-    private val fsi: PendingIntent = Mockito.mock(PendingIntent::class.java)
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        // Set up package manager mocks
-        whenever(packageManager.getApplicationIcon(anyString())).thenReturn(appIcon)
-        whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java)))
-            .thenReturn(appIcon)
-        whenever(packageManager.getApplicationLabel(any())).thenReturn(appName)
-        mContext.setMockPackageManager(packageManager)
-
-        fsiChromeRepo =
-            FsiChromeRepo(
-                mContext,
-                packageManager,
-                keyguardRepo,
-                launchFullScreenIntentProvider,
-                featureFlags,
-                uiBgExecutor,
-                dreamManager,
-                centralSurfaces
-            )
-    }
-
-    private fun createFsiEntry(fsi: PendingIntent): NotificationEntry {
-        val nb =
-            Notification.Builder(mContext, "a")
-                .setContentTitle("foo")
-                .setSmallIcon(R.drawable.sym_def_app_icon)
-                .setFullScreenIntent(fsi, /* highPriority= */ true)
-
-        val sbn =
-            StatusBarNotification(
-                "pkg",
-                "opPkg",
-                /* id= */ 0,
-                "tag" + System.currentTimeMillis(),
-                /* uid= */ 0,
-                /* initialPid */ 0,
-                nb.build(),
-                UserHandle(0),
-                /* overrideGroupKey= */ null,
-                /* postTime= */ 0
-            )
-
-        val entry = Mockito.mock(NotificationEntry::class.java)
-        whenever(entry.importance).thenReturn(NotificationManager.IMPORTANCE_HIGH)
-        whenever(entry.sbn).thenReturn(sbn)
-        return entry
-    }
-
-    @Test
-    fun testLaunchFullscreenIntent_flagNotEnabled_noLaunch() {
-        // Setup
-        featureFlags.set(Flags.FSI_CHROME, false)
-
-        // Test
-        val entry = createFsiEntry(fsi)
-        fsiChromeRepo.launchFullscreenIntent(entry)
-
-        // Verify
-        Mockito.verify(centralSurfaces, never()).wakeUpForFullScreenIntent()
-    }
-
-    @Test
-    fun testLaunchFullscreenIntent_notOnKeyguard_noLaunch() {
-        // Setup
-        featureFlags.set(Flags.FSI_CHROME, true)
-        keyguardRepo.setKeyguardShowing(false)
-
-        // Test
-        val entry = createFsiEntry(fsi)
-        fsiChromeRepo.launchFullscreenIntent(entry)
-
-        // Verify
-        Mockito.verify(centralSurfaces, never()).wakeUpForFullScreenIntent()
-    }
-
-    @Test
-    fun testLaunchFullscreenIntent_stopsScreensaver() {
-        // Setup
-        featureFlags.set(Flags.FSI_CHROME, true)
-        keyguardRepo.setKeyguardShowing(true)
-
-        // Test
-        val entry = createFsiEntry(fsi)
-        fsiChromeRepo.launchFullscreenIntent(entry)
-
-        // Verify
-        Mockito.verify(dreamManager, times(1)).awaken()
-    }
-
-    @Test
-    fun testLaunchFullscreenIntent_updatesFsiInfoFlow() {
-        // Setup
-        featureFlags.set(Flags.FSI_CHROME, true)
-        keyguardRepo.setKeyguardShowing(true)
-
-        // Test
-        val entry = createFsiEntry(fsi)
-        fsiChromeRepo.launchFullscreenIntent(entry)
-
-        // Verify
-        val expectedFsiInfo = FsiChromeRepo.FSIInfo(appName, appIcon, fsi)
-        assertEquals(expectedFsiInfo, fsiChromeRepo.infoFlow.value)
-    }
-
-    @Test
-    fun testLaunchFullscreenIntent_notifyFsiLaunched() {
-        // Setup
-        featureFlags.set(Flags.FSI_CHROME, true)
-        keyguardRepo.setKeyguardShowing(true)
-
-        // Test
-        val entry = createFsiEntry(fsi)
-        fsiChromeRepo.launchFullscreenIntent(entry)
-
-        // Verify
-        Mockito.verify(entry, times(1)).notifyFullScreenIntentLaunched()
-    }
-
-    @Test
-    fun testLaunchFullscreenIntent_wakesUpDevice() {
-        // Setup
-        featureFlags.set(Flags.FSI_CHROME, true)
-        keyguardRepo.setKeyguardShowing(true)
-
-        // Test
-        val entry = createFsiEntry(fsi)
-        fsiChromeRepo.launchFullscreenIntent(entry)
-
-        // Verify
-        Mockito.verify(centralSurfaces, times(1)).wakeUpForFullScreenIntent()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactoryTest.kt
deleted file mode 100644
index 5cee9e3..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactoryTest.kt
+++ /dev/null
@@ -1,112 +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.
- */
-
-package com.android.systemui.statusbar.notification.fsi
-
-import android.app.PendingIntent
-import android.graphics.drawable.Drawable
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.mockito.withArgCaptor
-import com.android.systemui.util.time.FakeSystemClock
-import com.android.wm.shell.TaskView
-import com.android.wm.shell.TaskViewFactory
-import com.google.common.truth.Truth.assertThat
-import java.util.Optional
-import java.util.function.Consumer
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.stateIn
-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
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper(setAsMainLooper = true)
-class FsiChromeViewModelFactoryTest : SysuiTestCase() {
-    @Mock private lateinit var taskViewFactoryOptional: Optional<TaskViewFactory>
-    @Mock private lateinit var taskViewFactory: TaskViewFactory
-    @Mock lateinit var taskView: TaskView
-
-    @Main var mainExecutor = FakeExecutor(FakeSystemClock())
-    lateinit var viewModelFactory: FsiChromeViewModelFactory
-
-    private val fakeInfoFlow = MutableStateFlow<FsiChromeRepo.FSIInfo?>(null)
-    private var fsiChromeRepo: FsiChromeRepo =
-        mock<FsiChromeRepo>().apply { whenever(infoFlow).thenReturn(fakeInfoFlow) }
-
-    private val appName = "appName"
-    private val appIcon: Drawable = context.getDrawable(com.android.systemui.R.drawable.ic_android)
-    private val fsi: PendingIntent = Mockito.mock(PendingIntent::class.java)
-    private val fsiInfo = FsiChromeRepo.FSIInfo(appName, appIcon, fsi)
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        whenever(taskViewFactoryOptional.get()).thenReturn(taskViewFactory)
-
-        viewModelFactory =
-            FsiChromeViewModelFactory(fsiChromeRepo, taskViewFactoryOptional, context, mainExecutor)
-    }
-
-    @Test
-    fun testViewModelFlow_update_createsTaskView() {
-        runTest {
-            val latestViewModel =
-                viewModelFactory.viewModelFlow
-                    .onStart { FsiDebug.log("viewModelFactory.viewModelFlow.onStart") }
-                    .stateIn(
-                        backgroundScope, // stateIn runs forever, don't count it as test coroutine
-                        SharingStarted.Eagerly,
-                        null
-                    )
-            runCurrent() // Drain queued backgroundScope operations
-
-            // Test: emit the fake FSIInfo
-            fakeInfoFlow.emit(fsiInfo)
-            runCurrent()
-
-            val taskViewFactoryCallback: Consumer<TaskView> = withArgCaptor {
-                verify(taskViewFactory).create(any(), any(), capture())
-            }
-            taskViewFactoryCallback.accept(taskView) // this will call k.resume
-            runCurrent()
-
-            // Verify that the factory has produced a new ViewModel
-            // containing the relevant data from FsiInfo
-            val expectedViewModel =
-                FsiChromeViewModel(appName, appIcon, taskView, fsi, fsiChromeRepo)
-
-            assertThat(latestViewModel.value).isEqualTo(expectedViewModel)
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 38e8228..db4bb45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -114,6 +114,7 @@
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
 import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.notetask.NoteTaskController;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.plugins.PluginDependencyProvider;
 import com.android.systemui.plugins.PluginManager;
@@ -259,6 +260,7 @@
     @Mock private StatusBarWindowStateController mStatusBarWindowStateController;
     @Mock private UserSwitcherController mUserSwitcherController;
     @Mock private Bubbles mBubbles;
+    @Mock private NoteTaskController mNoteTaskController;
     @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
     @Mock private NotificationIconAreaController mNotificationIconAreaController;
     @Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
@@ -472,6 +474,7 @@
                 mWakefulnessLifecycle,
                 mStatusBarStateController,
                 Optional.of(mBubbles),
+                () -> mNoteTaskController,
                 mDeviceProvisionedController,
                 mNavigationBarController,
                 mAccessibilityFloatingMenuController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index c0537a6..dc5a047 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -1356,33 +1356,10 @@
     }
 
     @Test
-    public void notificationAlpha_unnocclusionAnimating_bouncerActive_usesKeyguardNotifAlpha() {
-        when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
-        mScrimController.setClipsQsScrim(true);
-
-        mScrimController.transitionTo(ScrimState.KEYGUARD);
-        mScrimController.setUnocclusionAnimationRunning(true);
-
-        assertAlphaAfterExpansion(
-                mNotificationsScrim, ScrimState.KEYGUARD.getNotifAlpha(), /* expansion */ 0f);
-        assertAlphaAfterExpansion(
-                mNotificationsScrim, ScrimState.KEYGUARD.getNotifAlpha(), /* expansion */ 0.4f);
-        assertAlphaAfterExpansion(
-                mNotificationsScrim, ScrimState.KEYGUARD.getNotifAlpha(), /* expansion */ 1.0f);
-
-        // Verify normal behavior after
-        mScrimController.setUnocclusionAnimationRunning(false);
-        float expansion = 0.4f;
-        float alpha = 1 - BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansion);
-        assertAlphaAfterExpansion(mNotificationsScrim, alpha, expansion);
-    }
-
-    @Test
     public void notificationAlpha_unnocclusionAnimating_bouncerNotActive_usesKeyguardNotifAlpha() {
         when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
 
         mScrimController.transitionTo(ScrimState.KEYGUARD);
-        mScrimController.setUnocclusionAnimationRunning(true);
 
         assertAlphaAfterExpansion(
                 mNotificationsScrim, ScrimState.KEYGUARD.getNotifAlpha(), /* expansion */ 0f);
@@ -1392,7 +1369,6 @@
                 mNotificationsScrim, ScrimState.KEYGUARD.getNotifAlpha(), /* expansion */ 1.0f);
 
         // Verify normal behavior after
-        mScrimController.setUnocclusionAnimationRunning(false);
         float expansion = 0.4f;
         float alpha = 1 - ShadeInterpolation.getNotificationScrimAlpha(expansion);
         assertAlphaAfterExpansion(mNotificationsScrim, alpha, expansion);
@@ -1598,7 +1574,6 @@
 
     @Test
     public void setUnOccludingAnimationKeyguard() {
-        mScrimController.setUnocclusionAnimationRunning(true);
         mScrimController.transitionTo(ScrimState.KEYGUARD);
         finishAnimationsImmediately();
         assertThat(mNotificationsScrim.getViewAlpha())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 1aad83e..158e9ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -368,17 +368,6 @@
     }
 
     @Test
-    public void setOccluded_animatesPanelExpansion_onlyIfBouncerHidden() {
-        mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
-        verify(mCentralSurfaces).animateKeyguardUnoccluding();
-
-        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
-        clearInvocations(mCentralSurfaces);
-        mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
-        verify(mCentralSurfaces, never()).animateKeyguardUnoccluding();
-    }
-
-    @Test
     public void setOccluded_onKeyguardOccludedChangedCalled() {
         clearInvocations(mKeyguardStateController);
         clearInvocations(mKeyguardUpdateMonitor);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 1f8a779..25381be 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3928,6 +3928,8 @@
                         mGlobalClients.getRegisteredCallbackCookie(i);
                 pw.append(Arrays.toString(client.mPackageNames));
             }
+            pw.println();
+            mProxyManager.dump(fd, pw, args);
         }
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 094053e..0e25a06 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -910,6 +910,10 @@
             pw.println("     Top Focused Window Id = " + mTopFocusedWindowId);
             pw.println("     Accessibility Focused Window Id = " + mAccessibilityFocusedWindowId
                     + " ]");
+            if (mIsProxy) {
+                pw.println("Proxy accessibility focused window = "
+                        + mProxyDisplayAccessibilityFocusedWindow);
+            }
             pw.println();
             if (mWindows != null) {
                 final int windowCount = mWindows.size();
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index 945d43b..b19a502 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -38,14 +38,18 @@
 import android.os.RemoteException;
 import android.view.KeyEvent;
 import android.view.accessibility.AccessibilityDisplayProxy;
+import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityWindowInfo;
 
 import androidx.annotation.Nullable;
 
 import com.android.internal.R;
+import com.android.internal.util.DumpUtils;
 import com.android.server.wm.WindowManagerInternal;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
@@ -61,6 +65,8 @@
  * TODO(241429275): Initialize this when a proxy is registered.
  */
 public class ProxyAccessibilityServiceConnection extends AccessibilityServiceConnection {
+    private static final String LOG_TAG = "ProxyAccessibilityServiceConnection";
+
     private int mDisplayId;
     private List<AccessibilityServiceInfo> mInstalledAndEnabledServices;
 
@@ -565,4 +571,25 @@
     public void setAnimationScale(float scale) throws UnsupportedOperationException {
         throw new UnsupportedOperationException("setAnimationScale is not supported");
     }
+
+    @Override
+    public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
+        if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
+        synchronized (mLock) {
+            pw.append("Proxy[displayId=" + mDisplayId);
+            pw.append(", feedbackType"
+                    + AccessibilityServiceInfo.feedbackTypeToString(mFeedbackType));
+            pw.append(", capabilities=" + mAccessibilityServiceInfo.getCapabilities());
+            pw.append(", eventTypes="
+                    + AccessibilityEvent.eventTypeToString(mEventTypes));
+            pw.append(", notificationTimeout=" + mNotificationTimeout);
+            pw.append(", focusStrokeWidth=").append(String.valueOf(mFocusStrokeWidth));
+            pw.append(", focusColor=").append(String.valueOf(mFocusColor));
+            pw.append(", installedAndEnabledServiceCount=").append(String.valueOf(
+                    mInstalledAndEnabledServices.size()));
+            pw.append(", installedAndEnabledServices=").append(
+                    mInstalledAndEnabledServices.toString());
+            pw.append("]");
+        }
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index 9d91d10..e258de1 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -23,6 +23,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.accessibility.AccessibilityEvent;
@@ -30,6 +31,8 @@
 
 import com.android.server.wm.WindowManagerInternal;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.List;
 
 /**
@@ -42,6 +45,9 @@
  * TODO(262244375): Add unit tests.
  */
 public class ProxyManager {
+    private static final boolean DEBUG = false;
+    private static final String LOG_TAG = "ProxyManager";
+
     // Names used to populate ComponentName and ResolveInfo in connection.mA11yServiceInfo and in
     // the infos of connection.setInstalledAndEnabledServices
     static final String PROXY_COMPONENT_PACKAGE_NAME = "ProxyPackage";
@@ -79,6 +85,9 @@
             AbstractAccessibilityServiceConnection.SystemSupport systemSupport,
             AccessibilityTrace trace,
             WindowManagerInternal windowManagerInternal) throws RemoteException {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "Register proxy for display id: " + displayId);
+        }
 
         // Set a default AccessibilityServiceInfo that is used before the proxy's info is
         // populated. A proxy has the touch exploration and window capabilities.
@@ -134,6 +143,9 @@
             if (mProxyA11yServiceConnections.contains(displayId)) {
                 mProxyA11yServiceConnections.remove(displayId);
                 removed = true;
+                if (DEBUG) {
+                    Slog.v(LOG_TAG, "Unregister proxy for display id " + displayId);
+                }
             }
         }
         if (removed) {
@@ -155,19 +167,25 @@
      */
     public boolean isProxyed(int displayId) {
         synchronized (mLock) {
-            return mProxyA11yServiceConnections.contains(displayId);
+            final boolean tracked = mProxyA11yServiceConnections.contains(displayId);
+            if (DEBUG) {
+                Slog.v(LOG_TAG, "Tracking proxy display " + displayId + " : " + tracked);
+            }
+            return tracked;
         }
     }
 
     /**
-     * Sends AccessibilityEvents to all proxies.
-     * {@link android.view.accessibility.AccessibilityDisplayProxy} will filter based on display.
-     * TODO(b/250929565): Filtering should happen in the system, not in the proxy.
+     * Sends AccessibilityEvents to a proxy given the event's displayId.
      */
     public void sendAccessibilityEventLocked(AccessibilityEvent event) {
         final ProxyAccessibilityServiceConnection proxy =
                 mProxyA11yServiceConnections.get(event.getDisplayId());
         if (proxy != null) {
+            if (DEBUG) {
+                Slog.v(LOG_TAG, "Send proxy event " + event + " for display id "
+                        + event.getDisplayId());
+            }
             proxy.notifyAccessibilityEvent(event);
         }
     }
@@ -186,6 +204,9 @@
                 break;
             }
         }
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "At least one proxy can retrieve windows: " + observingWindows);
+        }
         return observingWindows;
     }
 
@@ -205,6 +226,14 @@
                 clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
             }
         }
+
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "Accessibility is enabled for all proxies: "
+                    + ((clientState & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0));
+            Slog.v(LOG_TAG, "Touch exploration is enabled for all proxies: "
+                    + ((clientState & AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED)
+                            != 0));
+        }
         return clientState;
         // TODO(b/254545943): When A11yManager is separated, include support for other properties.
     }
@@ -234,6 +263,10 @@
                     mProxyA11yServiceConnections.valueAt(i);
             relevantEventTypes |= proxy.getRelevantEventTypes();
         }
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "Relevant event types for all proxies: "
+                    + AccessibilityEvent.eventTypeToString(relevantEventTypes));
+        }
         return relevantEventTypes;
     }
 
@@ -275,4 +308,25 @@
     void setAccessibilityInputFilter(AccessibilityInputFilter filter) {
         mA11yInputFilter = filter;
     }
+
+
+    /**
+     * Prints information belonging to each display that is controlled by an
+     * AccessibilityDisplayProxy.
+     */
+    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        synchronized (mLock) {
+            pw.println();
+            pw.println("Proxy manager state:");
+            pw.println("    Number of proxy connections: " + mProxyA11yServiceConnections.size());
+            pw.println("    Registered proxy connections:");
+            for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+                final ProxyAccessibilityServiceConnection proxy =
+                        mProxyA11yServiceConnections.valueAt(i);
+                if (proxy != null) {
+                    proxy.dump(fd, pw, args);
+                }
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index ed5b858..3b1983f 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -84,7 +84,7 @@
     private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler);
 
     private static AtomicInteger sNextUniqueIndex = new AtomicInteger(
-            VirtualDeviceManager.DEVICE_ID_DEFAULT + 1);
+            Context.DEVICE_ID_DEFAULT + 1);
 
     /**
      * Mapping from device IDs to virtual devices.
@@ -327,7 +327,7 @@
         @Override // Binder call
         public int getDeviceIdForDisplayId(int displayId) {
             if (displayId == Display.INVALID_DISPLAY || displayId == Display.DEFAULT_DISPLAY) {
-                return VirtualDeviceManager.DEVICE_ID_DEFAULT;
+                return Context.DEVICE_ID_DEFAULT;
             }
             synchronized (mVirtualDeviceManagerLock) {
                 for (int i = 0; i < mVirtualDevices.size(); i++) {
@@ -337,7 +337,7 @@
                     }
                 }
             }
-            return VirtualDeviceManager.DEVICE_ID_DEFAULT;
+            return Context.DEVICE_ID_DEFAULT;
         }
 
         // Binder call
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 92e322f..e9a7f20 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -192,20 +192,21 @@
     private ArrayDeque<Bundle> mBatteryLevelsEventQueue;
     private long mLastBatteryLevelChangedSentMs;
 
-    private Bundle mBatteryChangedOptions = BroadcastOptions.makeRemovingMatchingFilter(
-            new IntentFilter(Intent.ACTION_BATTERY_CHANGED)).setDeferUntilActive(true)
+    private Bundle mBatteryChangedOptions = BroadcastOptions.makeBasic()
+            .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+            .setDeferUntilActive(true)
             .toBundle();
-    private Bundle mPowerConnectedOptions = BroadcastOptions.makeRemovingMatchingFilter(
-            new IntentFilter(Intent.ACTION_POWER_DISCONNECTED)).setDeferUntilActive(true)
+    /** Used for both connected/disconnected, so match using key */
+    private Bundle mPowerOptions = BroadcastOptions.makeBasic()
+            .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+            .setDeliveryGroupMatchingKey("android", Intent.ACTION_POWER_CONNECTED)
+            .setDeferUntilActive(true)
             .toBundle();
-    private Bundle mPowerDisconnectedOptions = BroadcastOptions.makeRemovingMatchingFilter(
-            new IntentFilter(Intent.ACTION_POWER_CONNECTED)).setDeferUntilActive(true)
-            .toBundle();
-    private Bundle mBatteryLowOptions = BroadcastOptions.makeRemovingMatchingFilter(
-            new IntentFilter(Intent.ACTION_BATTERY_OKAY)).setDeferUntilActive(true)
-            .toBundle();
-    private Bundle mBatteryOkayOptions = BroadcastOptions.makeRemovingMatchingFilter(
-            new IntentFilter(Intent.ACTION_BATTERY_LOW)).setDeferUntilActive(true)
+    /** Used for both low/okay, so match using key */
+    private Bundle mBatteryOptions = BroadcastOptions.makeBasic()
+            .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+            .setDeliveryGroupMatchingKey("android", Intent.ACTION_BATTERY_OKAY)
+            .setDeferUntilActive(true)
             .toBundle();
 
     private MetricsLogger mMetricsLogger;
@@ -636,7 +637,7 @@
                     @Override
                     public void run() {
                         mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
-                                mPowerConnectedOptions);
+                                mPowerOptions);
                     }
                 });
             }
@@ -648,7 +649,7 @@
                     @Override
                     public void run() {
                         mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
-                                mPowerDisconnectedOptions);
+                                mPowerOptions);
                     }
                 });
             }
@@ -662,7 +663,7 @@
                     @Override
                     public void run() {
                         mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
-                                mBatteryLowOptions);
+                                mBatteryOptions);
                     }
                 });
             } else if (mSentLowBatteryBroadcast &&
@@ -675,7 +676,7 @@
                     @Override
                     public void run() {
                         mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
-                                mBatteryOkayOptions);
+                                mBatteryOptions);
                     }
                 });
             }
@@ -1210,6 +1211,11 @@
     }
 
     private final class Led {
+        // must match: config_notificationsBatteryLowBehavior in config.xml
+        static final int LOW_BATTERY_BEHAVIOR_DEFAULT = 0;
+        static final int LOW_BATTERY_BEHAVIOR_SOLID = 1;
+        static final int LOW_BATTERY_BEHAVIOR_FLASHING = 2;
+
         private final LogicalLight mBatteryLight;
 
         private final int mBatteryLowARGB;
@@ -1217,6 +1223,7 @@
         private final int mBatteryFullARGB;
         private final int mBatteryLedOn;
         private final int mBatteryLedOff;
+        private final int mBatteryLowBehavior;
 
         public Led(Context context, LightsManager lights) {
             mBatteryLight = lights.getLight(LightsManager.LIGHT_ID_BATTERY);
@@ -1233,6 +1240,8 @@
                     com.android.internal.R.integer.config_notificationsBatteryLedOff);
             mBatteryNearlyFullLevel = context.getResources().getInteger(
                     com.android.internal.R.integer.config_notificationsBatteryNearlyFullLevel);
+            mBatteryLowBehavior = context.getResources().getInteger(
+                    com.android.internal.R.integer.config_notificationsBatteryLowBehavior);
         }
 
         /**
@@ -1245,13 +1254,26 @@
             final int level = mHealthInfo.batteryLevel;
             final int status = mHealthInfo.batteryStatus;
             if (level < mLowBatteryWarningLevel) {
-                if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
-                    // Solid red when battery is charging
-                    mBatteryLight.setColor(mBatteryLowARGB);
-                } else {
-                    // Flash red when battery is low and not charging
-                    mBatteryLight.setFlashing(mBatteryLowARGB, LogicalLight.LIGHT_FLASH_TIMED,
-                            mBatteryLedOn, mBatteryLedOff);
+                switch (mBatteryLowBehavior) {
+                    case LOW_BATTERY_BEHAVIOR_SOLID:
+                        // Solid red when low battery
+                        mBatteryLight.setColor(mBatteryLowARGB);
+                        break;
+                    case LOW_BATTERY_BEHAVIOR_FLASHING:
+                        // Flash red when battery is low and not charging
+                        mBatteryLight.setFlashing(mBatteryLowARGB, LogicalLight.LIGHT_FLASH_TIMED,
+                                mBatteryLedOn, mBatteryLedOff);
+                        break;
+                    default:
+                        if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
+                            // Solid red when battery is charging
+                            mBatteryLight.setColor(mBatteryLowARGB);
+                        } else {
+                            // Flash red when battery is low and not charging
+                            mBatteryLight.setFlashing(mBatteryLowARGB,
+                                    LogicalLight.LIGHT_FLASH_TIMED, mBatteryLedOn, mBatteryLedOff);
+                        }
+                        break;
                 }
             } else if (status == BatteryManager.BATTERY_STATUS_CHARGING
                     || status == BatteryManager.BATTERY_STATUS_FULL) {
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index c16314b..b352d31 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -4940,10 +4940,6 @@
             if (intent.getClipData() == null) {
                 intent.setClipData(ClipData.newPlainText(null, null));
             }
-            intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_READ_URI_PERMISSION
-                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-                    | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
-                    | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION));
             final long bid = Binder.clearCallingIdentity();
             try {
                 PackageManager pm = mContext.getPackageManager();
@@ -4989,7 +4985,19 @@
             if (intent == null) {
                 return (simulateIntent == null);
             }
-            return intent.filterEquals(simulateIntent);
+            if (!intent.filterEquals(simulateIntent)) {
+                return false;
+            }
+
+            if (intent.getSelector() != simulateIntent.getSelector()) {
+                return false;
+            }
+
+            int prohibitedFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION
+                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+                    | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+                    | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+            return (simulateIntent.getFlags() & prohibitedFlags) == 0;
         }
 
         private boolean isExportedSystemActivity(ActivityInfo activityInfo) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b898957..23529db 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18207,8 +18207,9 @@
                     bOptions.setTemporaryAppAllowlist(mInternal.getBootTimeTempAllowListDuration(),
                             TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
                             PowerExemptionManager.REASON_LOCALE_CHANGED, "");
-                    bOptions.setRemoveMatchingFilter(
-                            new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
+                    bOptions.setDeliveryGroupPolicy(
+                            BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+                    bOptions.setDeferUntilActive(true);
                     broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
                             null, null, OP_NONE, bOptions.toBundle(), false, false, MY_PID,
                             SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(),
diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java
index 463a2f8..16219cd 100644
--- a/services/core/java/com/android/server/am/AnrHelper.java
+++ b/services/core/java/com/android/server/am/AnrHelper.java
@@ -49,7 +49,7 @@
      * this time, the information might be outdated. So we only the dump the unresponsive process
      * instead of including other processes to avoid making the system more busy.
      */
-    private static final long EXPIRED_REPORT_TIME_MS = TimeUnit.MINUTES.toMillis(1);
+    private static final long EXPIRED_REPORT_TIME_MS = TimeUnit.SECONDS.toMillis(10);
 
     /**
      * If the last ANR occurred within this given time, consider it's anomaly.
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index b942f4b..0c21691 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -597,18 +597,6 @@
         final int cookie = traceBegin("enqueueBroadcast");
         r.applySingletonPolicy(mService);
 
-        final IntentFilter removeMatchingFilter = (r.options != null)
-                ? r.options.getRemoveMatchingFilter() : null;
-        if (removeMatchingFilter != null) {
-            final Predicate<Intent> removeMatching = removeMatchingFilter.asPredicate();
-            forEachMatchingBroadcast(QUEUE_PREDICATE_ANY, (testRecord, testIndex) -> {
-                // We only allow caller to remove broadcasts they enqueued
-                return (r.callingUid == testRecord.callingUid)
-                        && (r.userId == testRecord.userId)
-                        && removeMatching.test(testRecord.intent);
-            }, mBroadcastConsumerSkipAndCanceled, true);
-        }
-
         applyDeliveryGroupPolicy(r);
 
         r.enqueueTime = SystemClock.uptimeMillis();
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index cdf22aa..69ad152 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -64,7 +64,6 @@
     private final ArrayList<UserTemplate> mUnknownHALTemplates = new ArrayList<>();
     private final BiometricUtils<S> mBiometricUtils;
     private final Map<Integer, Long> mAuthenticatorIds;
-    private final List<S> mEnrolledList;
     private final boolean mHasEnrollmentsBeforeStarting;
     private BaseClientMonitor mCurrentTask;
     private boolean mFavorHalEnrollments = false;
@@ -135,13 +134,12 @@
     protected InternalCleanupClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             int userId, @NonNull String owner, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            @NonNull List<S> enrolledList, @NonNull BiometricUtils<S> utils,
+            @NonNull BiometricUtils<S> utils,
             @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, null /* token */, null /* ClientMonitorCallbackConverter */,
                 userId, owner, 0 /* cookie */, sensorId, logger, biometricContext);
         mBiometricUtils = utils;
         mAuthenticatorIds = authenticatorIds;
-        mEnrolledList = enrolledList;
         mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
     }
 
@@ -169,12 +167,16 @@
     public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
 
+        final List<S> enrolledList =
+                mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId());
+
         // Start enumeration. Removal will start if necessary, when enumeration is completed.
         mCurrentTask = getEnumerateClient(getContext(), mLazyDaemon, getToken(), getTargetUserId(),
-                getOwnerString(), mEnrolledList, mBiometricUtils, getSensorId(), getLogger(),
+                getOwnerString(), enrolledList, mBiometricUtils, getSensorId(), getLogger(),
                 getBiometricContext());
 
-        Slog.d(TAG, "Starting enumerate: " + mCurrentTask);
+        Slog.d(TAG, "Starting enumerate: " + mCurrentTask + " enrolledList size:"
+                + enrolledList.size());
         mCurrentTask.start(mEnumerateCallback);
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
index b0b23fa..f09d192 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
@@ -43,10 +43,10 @@
     FaceInternalCleanupClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner,
             int sensorId, @NonNull BiometricLogger logger,
-            @NonNull BiometricContext biometricContext, @NonNull List<Face> enrolledList,
+            @NonNull BiometricContext biometricContext,
             @NonNull BiometricUtils<Face> utils, @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
-                enrolledList, utils, authenticatorIds);
+                utils, authenticatorIds);
     }
 
     @Override
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 cf8ea86..1a53fec 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
@@ -597,14 +597,13 @@
     public void scheduleInternalCleanup(int sensorId, int userId,
             @Nullable ClientMonitorCallback callback, boolean favorHalEnrollments) {
         mHandler.post(() -> {
-            final List<Face> enrolledList = getEnrolledFaces(sensorId, userId);
             final FaceInternalCleanupClient client =
                     new FaceInternalCleanupClient(mContext,
                             mSensors.get(sensorId).getLazySession(), userId,
                             mContext.getOpPackageName(), sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
                                     BiometricsProtoEnums.CLIENT_UNKNOWN),
-                            mBiometricContext, enrolledList,
+                            mBiometricContext,
                             FaceUtils.getInstance(sensorId),
                             mSensors.get(sensorId).getAuthenticatorIds());
             if (favorHalEnrollments) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 0f22296..1e33c96 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -818,12 +818,11 @@
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
-            final List<Face> enrolledList = getEnrolledFaces(mSensorId, userId);
             final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
                     mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    mBiometricContext, enrolledList,
+                    mBiometricContext,
                     FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCompositeCallback(callback,
                     mBiometricStateCallback));
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
index d21a750..89a17c6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
@@ -42,10 +42,10 @@
     FaceInternalCleanupClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner,
             int sensorId, @NonNull BiometricLogger logger,
-            @NonNull BiometricContext biometricContext, @NonNull List<Face> enrolledList,
+            @NonNull BiometricContext biometricContext,
             @NonNull BiometricUtils<Face> utils, @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
-                enrolledList, utils, authenticatorIds);
+                utils, authenticatorIds);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
index c315ccf..ff9127f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
@@ -45,10 +45,9 @@
             @NonNull Supplier<AidlSession> lazyDaemon,
             int userId, @NonNull String owner, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            @NonNull List<Fingerprint> enrolledList,
             @NonNull FingerprintUtils utils, @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
-                enrolledList, utils, authenticatorIds);
+                utils, authenticatorIds);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index a833278..23b6f84 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -562,7 +562,6 @@
     public void scheduleInternalCleanup(int sensorId, int userId,
             @Nullable ClientMonitorCallback callback, boolean favorHalEnrollments) {
         mHandler.post(() -> {
-            final List<Fingerprint> enrolledList = getEnrolledFingerprints(sensorId, userId);
             final FingerprintInternalCleanupClient client =
                     new FingerprintInternalCleanupClient(mContext,
                             mSensors.get(sensorId).getLazySession(), userId,
@@ -570,7 +569,7 @@
                             createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
                                     BiometricsProtoEnums.CLIENT_UNKNOWN),
                             mBiometricContext,
-                            enrolledList, FingerprintUtils.getInstance(sensorId),
+                            FingerprintUtils.getInstance(sensorId),
                             mSensors.get(sensorId).getAuthenticatorIds());
             if (favorHalEnrollments) {
                 client.setFavorHalEnrollments();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 99c491a..9e6f4e4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -742,14 +742,12 @@
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
-            final List<Fingerprint> enrolledList = getEnrolledFingerprints(
-                    mSensorProperties.sensorId, userId);
             final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient(
                     mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    mBiometricContext, enrolledList,
+                    mBiometricContext,
                     FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, callback);
         });
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
index 5e7cf35..8b61f59 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
@@ -45,11 +45,10 @@
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
             @NonNull String owner, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            @NonNull List<Fingerprint> enrolledList,
             @NonNull BiometricUtils<Fingerprint> utils,
             @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
-                enrolledList, utils, authenticatorIds);
+                utils, authenticatorIds);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index dd92ffc..fab138b 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -18,9 +18,9 @@
 
 import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
 import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_INVALID;
 import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
+import static android.content.Context.DEVICE_ID_DEFAULT;
+import static android.content.Context.DEVICE_ID_INVALID;
 
 import android.Manifest;
 import android.annotation.NonNull;
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index 3bc4b54..3e2efdd 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -158,15 +158,14 @@
             final DreamRecord oldDream = mCurrentDream;
             mCurrentDream = new DreamRecord(token, name, isPreviewMode, canDoze, userId, wakeLock);
             if (oldDream != null) {
-                if (!oldDream.mWakingGently) {
-                    // We will stop these previous dreams once the new dream is started.
-                    mPreviousDreams.add(oldDream);
-                } else if (Objects.equals(oldDream.mName, mCurrentDream.mName)) {
+                if (Objects.equals(oldDream.mName, mCurrentDream.mName)) {
                     // We are attempting to start a dream that is currently waking up gently.
                     // Let's silently stop the old instance here to clear the dream state.
                     // This should happen after the new mCurrentDream is set to avoid announcing
                     // a "dream stopped" state.
                     stopDreamInstance(/* immediately */ true, "restarting same dream", oldDream);
+                } else {
+                    mPreviousDreams.add(oldDream);
                 }
             }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 0ea64ab..8c7658e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -178,6 +178,14 @@
     public abstract void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId);
 
     /**
+     * Switch the keyboard layout in response to a keyboard shortcut.
+     *
+     * @param direction {@code 1} to switch to the next subtype, {@code -1} to switch to the
+     *                           previous subtype.
+     */
+    public abstract void switchKeyboardLayout(int direction);
+
+    /**
      * Fake implementation of {@link InputMethodManagerInternal}.  All the methods do nothing.
      */
     private static final InputMethodManagerInternal NOP =
@@ -256,6 +264,10 @@
                 @Override
                 public void maybeFinishStylusHandwriting() {
                 }
+
+                @Override
+                public void switchKeyboardLayout(int direction) {
+                }
             };
 
     /**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 6310ba3..d397a0c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2095,7 +2095,7 @@
                     new ArrayMap<>();
             AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
             queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
-                    methodList, directBootAwareness);
+                    methodList, directBootAwareness, mSettings.getEnabledInputMethodNames());
             settings = new InputMethodSettings(mContext, methodMap, userId, true /* copyOnWrite */);
         }
         // filter caller's access to input methods
@@ -4066,17 +4066,22 @@
             if (!calledWithValidTokenLocked(token)) {
                 return false;
             }
-            final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
-                    onlyCurrentIme, mMethodMap.get(getSelectedMethodIdLocked()), mCurrentSubtype);
-            if (nextSubtype == null) {
-                return false;
-            }
-            setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(),
-                    nextSubtype.mSubtypeId);
-            return true;
+            return switchToNextInputMethodLocked(token, onlyCurrentIme);
         }
     }
 
+    @GuardedBy("ImfLock.class")
+    private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme) {
+        final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
+                onlyCurrentIme, mMethodMap.get(getSelectedMethodIdLocked()), mCurrentSubtype);
+        if (nextSubtype == null) {
+            return false;
+        }
+        setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(),
+                nextSubtype.mSubtypeId);
+        return true;
+    }
+
     @BinderThread
     private boolean shouldOfferSwitchingToNextInputMethod(@NonNull IBinder token) {
         synchronized (ImfLock.class) {
@@ -4152,7 +4157,7 @@
                     new ArrayMap<>();
             AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
             queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
-                    methodList, DirectBootAwareness.AUTO);
+                    methodList, DirectBootAwareness.AUTO, mSettings.getEnabledInputMethodNames());
             final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap,
                     userId, false);
             settings.setAdditionalInputMethodSubtypes(imiId, toBeAdded, additionalSubtypeMap,
@@ -5049,7 +5054,7 @@
     static void queryInputMethodServicesInternal(Context context,
             @UserIdInt int userId, ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
             ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
-            @DirectBootAwareness int directBootAwareness) {
+            @DirectBootAwareness int directBootAwareness, List<String> enabledInputMethodList) {
         final Context userAwareContext = context.getUserId() == userId
                 ? context
                 : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
@@ -5082,6 +5087,17 @@
         methodList.ensureCapacity(services.size());
         methodMap.ensureCapacity(services.size());
 
+        filterInputMethodServices(additionalSubtypeMap, methodMap, methodList,
+                enabledInputMethodList, userAwareContext, services);
+    }
+
+    static void filterInputMethodServices(
+            ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
+            ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
+            List<String> enabledInputMethodList, Context userAwareContext,
+            List<ResolveInfo> services) {
+        final ArrayMap<String, Integer> imiPackageCount = new ArrayMap<>();
+
         for (int i = 0; i < services.size(); ++i) {
             ResolveInfo ri = services.get(i);
             ServiceInfo si = ri.serviceInfo;
@@ -5101,10 +5117,21 @@
                 if (imi.isVrOnly()) {
                     continue;  // Skip VR-only IME, which isn't supported for now.
                 }
-                methodList.add(imi);
-                methodMap.put(imi.getId(), imi);
-                if (DEBUG) {
-                    Slog.d(TAG, "Found an input method " + imi);
+                final String packageName = si.packageName;
+                // only include IMEs which are from the system, enabled, or below the threshold
+                if (si.applicationInfo.isSystemApp() || enabledInputMethodList.contains(imi.getId())
+                        || imiPackageCount.getOrDefault(packageName, 0)
+                        < InputMethodInfo.MAX_IMES_PER_PACKAGE) {
+                    imiPackageCount.put(packageName,
+                            1 + imiPackageCount.getOrDefault(packageName, 0));
+
+                    methodList.add(imi);
+                    methodMap.put(imi.getId(), imi);
+                    if (DEBUG) {
+                        Slog.d(TAG, "Found an input method " + imi);
+                    }
+                } else if (DEBUG) {
+                    Slog.d(TAG, "Found an input method, but ignored due threshold: " + imi);
                 }
             } catch (Exception e) {
                 Slog.wtf(TAG, "Unable to load input method " + imeId, e);
@@ -5126,7 +5153,8 @@
         mMyPackageMonitor.clearKnownImePackageNamesLocked();
 
         queryInputMethodServicesInternal(mContext, mSettings.getCurrentUserId(),
-                mAdditionalSubtypeMap, mMethodMap, mMethodList, DirectBootAwareness.AUTO);
+                mAdditionalSubtypeMap, mMethodMap, mMethodList, DirectBootAwareness.AUTO,
+                mSettings.getEnabledInputMethodNames());
 
         // Construct the set of possible IME packages for onPackageChanged() to avoid false
         // negatives when the package state remains to be the same but only the component state is
@@ -5185,7 +5213,7 @@
                             reenableMinimumNonAuxSystemImes);
             final int numImes = defaultEnabledIme.size();
             for (int i = 0; i < numImes; ++i) {
-                final InputMethodInfo imi =  defaultEnabledIme.get(i);
+                final InputMethodInfo imi = defaultEnabledIme.get(i);
                 if (DEBUG) {
                     Slog.d(TAG, "--- enable ime = " + imi);
                 }
@@ -5485,7 +5513,8 @@
                 new ArrayMap<>();
         AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
         queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
-                methodMap, methodList, DirectBootAwareness.AUTO);
+                methodMap, methodList, DirectBootAwareness.AUTO,
+                mSettings.getEnabledInputMethodNames());
         return methodMap;
     }
 
@@ -5741,6 +5770,17 @@
             mHandler.removeMessages(MSG_FINISH_HANDWRITING);
             mHandler.obtainMessage(MSG_FINISH_HANDWRITING).sendToTarget();
         }
+
+        @Override
+        public void switchKeyboardLayout(int direction) {
+            synchronized (ImfLock.class) {
+                if (direction > 0) {
+                    switchToNextInputMethodLocked(null /* token */, true /* onlyCurrentIme */);
+                } else {
+                    // TODO(b/258853866): Support backwards switching.
+                }
+            }
+        }
     }
 
     @BinderThread
@@ -6445,7 +6485,8 @@
                                 new ArrayMap<>();
                         AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
                         queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
-                                methodMap, methodList, DirectBootAwareness.AUTO);
+                                methodMap, methodList, DirectBootAwareness.AUTO,
+                                mSettings.getEnabledInputMethodNames());
                         final InputMethodSettings settings = new InputMethodSettings(mContext,
                                 methodMap, userId, false);
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 559eb53..17536fc 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -438,6 +438,15 @@
                     mSubtypeSplitter);
         }
 
+        List<String> getEnabledInputMethodNames() {
+            List<String> result = new ArrayList<>();
+            for (Pair<String, ArrayList<String>> pair :
+                    getEnabledInputMethodsAndSubtypeListLocked()) {
+                result.add(pair.first);
+            }
+            return result;
+        }
+
         void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
             if (reloadInputMethodStr) {
                 getEnabledInputMethodsStr();
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 1cc958b..fa2ba21 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -296,6 +296,8 @@
                 refreshAppOpsRestrictions(userId);
             }
         });
+        mInjector.getEmergencyHelper().addOnEmergencyStateChangedListener(
+                this::onEmergencyStateChanged);
 
         // set up passive provider first since it will be required for all other location providers,
         // which are loaded later once the system is ready.
@@ -567,6 +569,11 @@
         refreshAppOpsRestrictions(userId);
     }
 
+    private void onEmergencyStateChanged() {
+        boolean isInEmergency = mInjector.getEmergencyHelper().isInEmergency(Long.MIN_VALUE);
+        mInjector.getLocationUsageLogger().logEmergencyStateChanged(isInEmergency);
+    }
+
     private void logLocationEnabledState() {
         boolean locationEnabled = false;
         // Location setting is considered on if it is enabled for any one user
diff --git a/services/core/java/com/android/server/location/injector/EmergencyHelper.java b/services/core/java/com/android/server/location/injector/EmergencyHelper.java
index be4bf50..10cf714 100644
--- a/services/core/java/com/android/server/location/injector/EmergencyHelper.java
+++ b/services/core/java/com/android/server/location/injector/EmergencyHelper.java
@@ -16,14 +16,55 @@
 
 package com.android.server.location.injector;
 
+import java.util.concurrent.CopyOnWriteArrayList;
+
 /**
  * Provides helpers for emergency sessions.
  */
 public abstract class EmergencyHelper {
 
+    private final CopyOnWriteArrayList<EmergencyStateChangedListener> mListeners;
+
+    protected EmergencyHelper() {
+        mListeners = new CopyOnWriteArrayList<>();
+    }
+
+    /**
+     * Listener for emergency state changes.
+     */
+    public interface EmergencyStateChangedListener {
+        /**
+         * Called when state changes.
+         */
+        void onStateChanged();
+    }
+
     /**
      * Returns true if the device is in an emergency session, or if an emergency session ended
      * within the given extension time.
      */
     public abstract boolean isInEmergency(long extensionTimeMs);
+
+    /**
+     * Add a listener for changes to the emergency location state.
+     */
+    public void addOnEmergencyStateChangedListener(EmergencyStateChangedListener listener) {
+        mListeners.add(listener);
+    }
+
+    /**
+     * Remove a listener for changes to the emergency location state.
+     */
+    public void removeOnEmergencyStateChangedListener(EmergencyStateChangedListener listener) {
+        mListeners.remove(listener);
+    }
+
+    /**
+     * Notify listeners for emergency state of state change
+     */
+    protected final void dispatchEmergencyStateChanged() {
+        for (EmergencyStateChangedListener listener : mListeners) {
+            listener.onStateChanged();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/location/injector/Injector.java b/services/core/java/com/android/server/location/injector/Injector.java
index b2c8672..4a0c4b2 100644
--- a/services/core/java/com/android/server/location/injector/Injector.java
+++ b/services/core/java/com/android/server/location/injector/Injector.java
@@ -16,13 +16,11 @@
 
 package com.android.server.location.injector;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.location.settings.LocationSettings;
 
 /**
  * Injects various location dependencies so that they may be controlled by tests.
  */
-@VisibleForTesting
 public interface Injector {
 
     /** Returns a UserInfoHelper. */
diff --git a/services/core/java/com/android/server/location/injector/LocationUsageLogger.java b/services/core/java/com/android/server/location/injector/LocationUsageLogger.java
index a9701b3..9319e89 100644
--- a/services/core/java/com/android/server/location/injector/LocationUsageLogger.java
+++ b/services/core/java/com/android/server/location/injector/LocationUsageLogger.java
@@ -129,6 +129,13 @@
         FrameworkStatsLog.write(FrameworkStatsLog.LOCATION_ENABLED_STATE_CHANGED, enabled);
     }
 
+    /**
+     * Log emergency location state change event
+     */
+    public synchronized void logEmergencyStateChanged(boolean isInEmergency) {
+        FrameworkStatsLog.write(FrameworkStatsLog.EMERGENCY_STATE_CHANGED, isInEmergency);
+    }
+
     private static int bucketizeProvider(String provider) {
         if (LocationManager.NETWORK_PROVIDER.equals(provider)) {
             return LocationStatsEnums.PROVIDER_NETWORK;
diff --git a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
index 1fb00ef..c772e08 100644
--- a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
@@ -27,6 +27,7 @@
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
+import com.android.internal.telephony.TelephonyIntents;
 import com.android.server.FgThread;
 
 import java.util.Objects;
@@ -73,12 +74,25 @@
                     try {
                         mIsInEmergencyCall = mTelephonyManager.isEmergencyNumber(
                                 intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER));
+                        dispatchEmergencyStateChanged();
                     } catch (IllegalStateException e) {
                         Log.w(TAG, "Failed to call TelephonyManager.isEmergencyNumber().", e);
                     }
                 }
             }
         }, new IntentFilter(Intent.ACTION_NEW_OUTGOING_CALL));
+
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (!TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(
+                        intent.getAction())) {
+                    return;
+                }
+
+                dispatchEmergencyStateChanged();
+            }
+        }, new IntentFilter(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED));
     }
 
     @Override
@@ -108,6 +122,7 @@
                     if (mIsInEmergencyCall) {
                         mEmergencyCallEndRealtimeMs = SystemClock.elapsedRealtime();
                         mIsInEmergencyCall = false;
+                        dispatchEmergencyStateChanged();
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 70b8ce0..ce9fdda 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -420,7 +420,7 @@
         try (LockscreenCredential unifiedProfilePassword = generateRandomProfilePassword()) {
             setLockCredentialInternal(unifiedProfilePassword, profileUserPassword, profileUserId,
                     /* isLockTiedToParent= */ true);
-            tieProfileLockToParent(profileUserId, unifiedProfilePassword);
+            tieProfileLockToParent(profileUserId, parentId, unifiedProfilePassword);
             mManagedProfilePasswordCache.storePassword(profileUserId, unifiedProfilePassword);
         }
     }
@@ -1873,34 +1873,43 @@
     }
 
     @VisibleForTesting /** Note: this method is overridden in unit tests */
-    protected void tieProfileLockToParent(int userId, LockscreenCredential password) {
-        if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
+    protected void tieProfileLockToParent(int profileUserId, int parentUserId,
+            LockscreenCredential password) {
+        if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + profileUserId);
         final byte[] iv;
         final byte[] ciphertext;
+        final long parentSid;
+        try {
+            parentSid = getGateKeeperService().getSecureUserId(parentUserId);
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Failed to talk to GateKeeper service", e);
+        }
+
         try {
             KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
             keyGenerator.init(new SecureRandom());
             SecretKey secretKey = keyGenerator.generateKey();
             try {
                 mJavaKeyStore.setEntry(
-                        PROFILE_KEY_NAME_ENCRYPT + userId,
+                        PROFILE_KEY_NAME_ENCRYPT + profileUserId,
                         new java.security.KeyStore.SecretKeyEntry(secretKey),
                         new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
                                 .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                                 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                                 .build());
                 mJavaKeyStore.setEntry(
-                        PROFILE_KEY_NAME_DECRYPT + userId,
+                        PROFILE_KEY_NAME_DECRYPT + profileUserId,
                         new java.security.KeyStore.SecretKeyEntry(secretKey),
                         new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
                                 .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                                 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                                 .setUserAuthenticationRequired(true)
+                                .setBoundToSpecificSecureUserId(parentSid)
                                 .setUserAuthenticationValidityDurationSeconds(30)
                                 .build());
                 // Key imported, obtain a reference to it.
                 SecretKey keyStoreEncryptionKey = (SecretKey) mJavaKeyStore.getKey(
-                        PROFILE_KEY_NAME_ENCRYPT + userId, null);
+                        PROFILE_KEY_NAME_ENCRYPT + profileUserId, null);
                 Cipher cipher = Cipher.getInstance(
                         KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
                                 + KeyProperties.ENCRYPTION_PADDING_NONE);
@@ -1909,7 +1918,7 @@
                 iv = cipher.getIV();
             } finally {
                 // The original key can now be discarded.
-                mJavaKeyStore.deleteEntry(PROFILE_KEY_NAME_ENCRYPT + userId);
+                mJavaKeyStore.deleteEntry(PROFILE_KEY_NAME_ENCRYPT + profileUserId);
             }
         } catch (UnrecoverableKeyException
                 | BadPaddingException | IllegalBlockSizeException | KeyStoreException
@@ -1919,7 +1928,7 @@
         if (iv.length != PROFILE_KEY_IV_SIZE) {
             throw new IllegalArgumentException("Invalid iv length: " + iv.length);
         }
-        mStorage.writeChildProfileLock(userId, ArrayUtils.concat(iv, ciphertext));
+        mStorage.writeChildProfileLock(profileUserId, ArrayUtils.concat(iv, ciphertext));
     }
 
     private void setUserKeyProtection(@UserIdInt int userId, byte[] secret) {
@@ -2031,7 +2040,7 @@
                 LockscreenCredential piUserDecryptedPassword = profileUserDecryptedPasswords.get(i);
                 if (piUserId != -1 && piUserDecryptedPassword != null) {
                     if (DEBUG) Slog.v(TAG, "Restore tied profile lock");
-                    tieProfileLockToParent(piUserId, piUserDecryptedPassword);
+                    tieProfileLockToParent(piUserId, userId, piUserDecryptedPassword);
                 }
                 if (piUserDecryptedPassword != null) {
                     piUserDecryptedPassword.zeroize();
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index e592a22..d070b41 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -780,6 +780,10 @@
         int slot = loadWeaverSlot(protectorId, userId);
         destroyState(WEAVER_SLOT_NAME, protectorId, userId);
         if (slot != INVALID_WEAVER_SLOT) {
+            if (!isWeaverAvailable()) {
+                Slog.e(TAG, "Cannot erase Weaver slot because Weaver is unavailable");
+                return;
+            }
             Set<Integer> usedSlots = getUsedWeaverSlots();
             if (!usedSlots.contains(slot)) {
                 Slog.i(TAG, "Destroy weaver slot " + slot + " for user " + userId);
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index faa06f7..9bca9f0 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -404,7 +404,7 @@
                         "BackgroundDexOptService_" + (isPostBootUpdateJob ? "PostBoot" : "Idle"),
                         () -> {
                             TimingsTraceAndSlog tr =
-                                    new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_PACKAGE_MANAGER);
+                                    new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_DALVIK);
                             tr.traceBegin("jobExecution");
                             boolean completed = false;
                             boolean fatalError = false;
@@ -494,6 +494,8 @@
     @GuardedBy("mLock")
     private void waitForDexOptThreadToFinishLocked() {
         TimingsTraceAndSlog tr = new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_PACKAGE_MANAGER);
+        // This tracing section doesn't have any correspondence in ART Service - it never waits for
+        // cancellation to finish.
         tr.traceBegin("waitForDexOptThreadToFinishLocked");
         try {
             // Wait but check in regular internal to see if the thread is still alive.
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 0fd81ac..a9d4115 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -16,7 +16,7 @@
 
 package com.android.server.pm;
 
-import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+import static android.os.Trace.TRACE_TAG_DALVIK;
 
 import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
 import static com.android.server.pm.ApexManager.ActiveApexInfo;
@@ -470,11 +470,11 @@
 
     @DexOptResult
     private int performDexOptTraced(DexoptOptions options) {
-        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
+        Trace.traceBegin(TRACE_TAG_DALVIK, "dexopt");
         try {
             return performDexOptInternal(options);
         } finally {
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+            Trace.traceEnd(TRACE_TAG_DALVIK);
         }
     }
 
@@ -605,7 +605,7 @@
             throw new IllegalArgumentException("Can't dexopt APEX package: " + packageName);
         }
 
-        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
+        Trace.traceBegin(TRACE_TAG_DALVIK, "dexopt");
 
         // Whoever is calling forceDexOpt wants a compiled package.
         // Don't use profiles since that may cause compilation to be skipped.
@@ -615,7 +615,7 @@
 
         @DexOptResult int res = performDexOptInternalWithDependenciesLI(pkg, packageState, options);
 
-        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        Trace.traceEnd(TRACE_TAG_DALVIK);
         if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
             throw new IllegalStateException("Failed to dexopt: " + res);
         }
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index f338137..0a90e7a3 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -386,7 +386,7 @@
                             options.getCompilationReason());
                     // OTAPreopt doesn't have stats so don't report in that case.
                     if (packageStats != null) {
-                        Trace.traceBegin(Trace.TRACE_TAG_PACKAGE_MANAGER, "dex2oat-metrics");
+                        Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "dex2oat-metrics");
                         try {
                             long sessionId = sRandom.nextLong();
                             ArtStatsLogUtils.writeStatsLog(
@@ -403,7 +403,7 @@
                                     dexCodeIsa,
                                     path);
                         } finally {
-                            Trace.traceEnd(Trace.TRACE_TAG_PACKAGE_MANAGER);
+                            Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
                         }
                     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 7cc8e48..b157781 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6676,24 +6676,16 @@
         @Deprecated
         public void legacyDumpProfiles(String packageName, boolean dumpClassesAndMethods)
                 throws LegacyDexoptDisabledException {
-            /* Only the shell, root, or the app user should be able to dump profiles. */
-            final int callingUid = Binder.getCallingUid();
             final Computer snapshot = snapshotComputer();
-            final String[] callerPackageNames = snapshot.getPackagesForUid(callingUid);
-            if (!PackageManagerServiceUtils.isRootOrShell(callingUid)
-                    && !ArrayUtils.contains(callerPackageNames, packageName)) {
-                throw new SecurityException("dumpProfiles");
-            }
-
             AndroidPackage pkg = snapshot.getPackage(packageName);
             if (pkg == null) {
                 throw new IllegalArgumentException("Unknown package: " + packageName);
             }
 
             synchronized (mInstallLock) {
-                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dump profiles");
+                Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "dump profiles");
                 mArtManagerService.dumpProfiles(pkg, dumpClassesAndMethods);
-                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+                Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
             }
         }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 41592bd..586e112 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -391,6 +391,11 @@
     private int runLegacyDexoptCommand(@NonNull String cmd)
             throws RemoteException, LegacyDexoptDisabledException {
         Installer.checkLegacyDexoptDisabled();
+
+        if (!PackageManagerServiceUtils.isRootOrShell(Binder.getCallingUid())) {
+            throw new SecurityException("Dexopt shell commands need root or shell access");
+        }
+
         switch (cmd) {
             case "compile":
                 return runCompile();
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index f308931..37877cf 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -89,6 +89,7 @@
 
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RecentTaskInfo;
@@ -160,6 +161,7 @@
 import android.service.vr.IPersistentVrStateCallbacks;
 import android.speech.RecognizerIntent;
 import android.telecom.TelecomManager;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.MutableBoolean;
@@ -398,7 +400,6 @@
     ActivityTaskManagerInternal mActivityTaskManagerInternal;
     AutofillManagerInternal mAutofillManagerInternal;
     InputManagerInternal mInputManagerInternal;
-    InputMethodManagerInternal mInputMethodManagerInternal;
     DreamManagerInternal mDreamManagerInternal;
     PowerManagerInternal mPowerManagerInternal;
     IStatusBarService mStatusBarService;
@@ -659,6 +660,7 @@
     private static final int MSG_HANDLE_ALL_APPS = 22;
     private static final int MSG_LAUNCH_ASSIST = 23;
     private static final int MSG_RINGER_TOGGLE_CHORD = 24;
+    private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 25;
 
     private class PolicyHandler extends Handler {
         @Override
@@ -729,6 +731,9 @@
                 case MSG_SCREENSHOT_CHORD:
                     handleScreenShot(msg.arg1);
                     break;
+                case MSG_SWITCH_KEYBOARD_LAYOUT:
+                    handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
+                    break;
             }
         }
     }
@@ -1025,14 +1030,8 @@
                     break;
                 case SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME: {
                     if (mDismissImeOnBackKeyPressed) {
-                        if (mInputMethodManagerInternal == null) {
-                            mInputMethodManagerInternal =
-                                    LocalServices.getService(InputMethodManagerInternal.class);
-                        }
-                        if (mInputMethodManagerInternal != null) {
-                            mInputMethodManagerInternal.hideCurrentInputMethod(
+                        InputMethodManagerInternal.get().hideCurrentInputMethod(
                                     SoftInputShowHideReason.HIDE_POWER_BUTTON_GO_HOME);
-                        }
                     } else {
                         shortPressPowerGoHome();
                     }
@@ -3170,7 +3169,7 @@
                 }
                 if (down && repeatCount == 0) {
                     int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
-                    mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
+                    sendSwitchKeyboardLayout(event, direction);
                     return key_consumed;
                 }
                 break;
@@ -3420,7 +3419,7 @@
                     if (KeyEvent.metaStateHasModifiers(metaState & ~KeyEvent.META_SHIFT_MASK,
                             KeyEvent.META_CTRL_ON)) {
                         int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
-                        mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
+                        sendSwitchKeyboardLayout(event, direction);
                         return true;
                     }
                 }
@@ -3446,6 +3445,19 @@
         return false;
     }
 
+    private void sendSwitchKeyboardLayout(@NonNull KeyEvent event, int direction) {
+        mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, event.getDeviceId(),
+                direction).sendToTarget();
+    }
+
+    private void handleSwitchKeyboardLayout(int deviceId, int direction) {
+        if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
+            InputMethodManagerInternal.get().switchKeyboardLayout(direction);
+        } else {
+            mWindowManagerFuncs.switchKeyboardLayout(deviceId, direction);
+        }
+    }
+
     private boolean interceptFallback(IBinder focusedToken, KeyEvent fallbackEvent,
             int policyFlags) {
         int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags);
diff --git a/services/core/java/com/android/server/security/FileIntegrity.java b/services/core/java/com/android/server/security/FileIntegrity.java
index 8047638..b8f187e 100644
--- a/services/core/java/com/android/server/security/FileIntegrity.java
+++ b/services/core/java/com/android/server/security/FileIntegrity.java
@@ -46,8 +46,9 @@
      */
     @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
     public static void setUpFsVerity(@NonNull File file) throws IOException {
-        ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, MODE_READ_ONLY);
-        setUpFsVerity(pfd);
+        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, MODE_READ_ONLY)) {
+            setUpFsVerity(pfd);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 6500700..e463358 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -102,6 +102,7 @@
 import android.app.servertransaction.ResumeActivityItem;
 import android.companion.virtual.VirtualDeviceManager;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
@@ -1235,7 +1236,7 @@
 
     int getDeviceIdForDisplayId(int displayId) {
         if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY)  {
-            return VirtualDeviceManager.DEVICE_ID_DEFAULT;
+            return Context.DEVICE_ID_DEFAULT;
         }
         if (mVirtualDeviceManager == null) {
             mVirtualDeviceManager =
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 97e0b1e..ce9bff8 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -948,7 +948,7 @@
         synchronized (mService.mGlobalLock) {
             WindowState windowState = mService.windowForClientLocked(this, window, false);
             if (windowState == null) {
-                Slog.e(TAG_WM,
+                Slog.i(TAG_WM,
                         "setOnBackInvokedCallback(): No window state for package:" + mPackageName);
             } else {
                 windowState.setOnBackInvokedCallbackInfo(callbackInfo);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index e56b679..7c0318d 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -54,7 +54,6 @@
 import android.app.IApplicationThread;
 import android.app.ProfilerInfo;
 import android.app.servertransaction.ConfigurationChangeItem;
-import android.companion.virtual.VirtualDeviceManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -211,7 +210,7 @@
     /** Whether {@link #mLastReportedConfiguration} is deferred by the cached state. */
     private volatile boolean mHasCachedConfiguration;
 
-    private int mTopActivityDeviceId = VirtualDeviceManager.DEVICE_ID_DEFAULT;
+    private int mTopActivityDeviceId = Context.DEVICE_ID_DEFAULT;
     /**
      * Registered {@link DisplayArea} as a listener to override config changes. {@code null} if not
      * registered.
@@ -1435,7 +1434,7 @@
         // TODO(b/263402938): Add tests that capture the deviceId dispatch to the client.
         mTopActivityDeviceId = deviceId;
         dispatchConfiguration(config, topActivityDeviceChanged ? mTopActivityDeviceId
-                : VirtualDeviceManager.DEVICE_ID_INVALID);
+                : Context.DEVICE_ID_INVALID);
     }
 
     private int getTopActivityDeviceId() {
@@ -1520,7 +1519,7 @@
 
     private void scheduleConfigurationChange(IApplicationThread thread, Configuration config) {
         // By default send invalid deviceId as no-op signal so it's not updated on the client side.
-        scheduleConfigurationChange(thread, config, VirtualDeviceManager.DEVICE_ID_INVALID);
+        scheduleConfigurationChange(thread, config, Context.DEVICE_ID_INVALID);
     }
 
     private void scheduleConfigurationChange(IApplicationThread thread, Configuration config,
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index 05a8b11..07ddda3 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -52,6 +52,10 @@
         "android.test.runner",
     ],
 
+    data: [
+        ":SimpleTestIme",
+    ],
+
     certificate: "platform",
     platform_apis: true,
     test_suites: ["device-tests"],
diff --git a/services/tests/InputMethodSystemServerTests/AndroidTest.xml b/services/tests/InputMethodSystemServerTests/AndroidTest.xml
index 92be780..1371934 100644
--- a/services/tests/InputMethodSystemServerTests/AndroidTest.xml
+++ b/services/tests/InputMethodSystemServerTests/AndroidTest.xml
@@ -21,6 +21,7 @@
         <option name="cleanup-apks" value="true" />
         <option name="install-arg" value="-t" />
         <option name="test-file-name" value="FrameworksInputMethodSystemServerTests.apk" />
+        <option name="test-file-name" value="SimpleTestIme.apk" />
     </target_preparer>
 
     <option name="test-tag" value="FrameworksInputMethodSystemServerTests" />
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java
new file mode 100644
index 0000000..7cbfc52
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.inputmethod;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.util.ArrayMap;
+import android.view.inputmethod.InputMethod;
+import android.view.inputmethod.InputMethodInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class InputMethodManagerServiceRestrictImeAmountTest extends
+        InputMethodManagerServiceTestBase {
+
+    @Test
+    public void testFilterInputMethodServices_loadsAllImesBelowThreshold() {
+        List<ResolveInfo> resolveInfoList = new ArrayList<>();
+        for (int i = 0; i < 5; i++) {
+            resolveInfoList.add(
+                    createFakeResolveInfo("com.android.apps.inputmethod.simpleime", "IME" + i));
+        }
+
+        final List<InputMethodInfo> methodList = filterInputMethodServices(resolveInfoList,
+                List.of());
+        assertEquals(5, methodList.size());
+    }
+
+    @Test
+    public void testFilterInputMethodServices_ignoresImesBeyondThreshold() {
+        List<ResolveInfo> resolveInfoList = new ArrayList<>();
+        for (int i = 0; i < 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE; i++) {
+            resolveInfoList.add(
+                    createFakeResolveInfo("com.android.apps.inputmethod.simpleime", "IME" + i));
+        }
+
+        final List<InputMethodInfo> methodList = filterInputMethodServices(resolveInfoList,
+                List.of());
+        assertWithMessage("Filtered IMEs").that(methodList.size()).isEqualTo(
+                InputMethodInfo.MAX_IMES_PER_PACKAGE);
+    }
+
+    @Test
+    public void testFilterInputMethodServices_loadsSystemImesBeyondThreshold() {
+        List<ResolveInfo> resolveInfoList = new ArrayList<>();
+        for (int i = 0; i < 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE; i++) {
+            resolveInfoList.add(
+                    createFakeSystemResolveInfo("com.android.apps.inputmethod.systemime",
+                            "SystemIME" + i));
+        }
+
+        final List<InputMethodInfo> methodList = filterInputMethodServices(resolveInfoList,
+                List.of());
+        assertWithMessage("Filtered IMEs").that(methodList.size()).isEqualTo(
+                2 * InputMethodInfo.MAX_IMES_PER_PACKAGE);
+    }
+
+    @Test
+    public void testFilterInputMethodServices_ignoresImesBeyondThresholdFromTwoPackages() {
+        List<ResolveInfo> resolveInfoList = new ArrayList<>();
+        for (int i = 0; i < 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE; i++) {
+            resolveInfoList.add(
+                    createFakeResolveInfo("com.android.apps.inputmethod.simpleime1", "IME1_" + i));
+        }
+        for (int i = 0; i < 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE; i++) {
+            resolveInfoList.add(
+                    createFakeResolveInfo("com.android.apps.inputmethod.simpleime2", "IME2_" + i));
+        }
+
+        final List<InputMethodInfo> methodList = filterInputMethodServices(resolveInfoList,
+                List.of());
+        assertWithMessage("Filtered IMEs").that(methodList.size()).isEqualTo(
+                2 * InputMethodInfo.MAX_IMES_PER_PACKAGE);
+    }
+
+    @Test
+    public void testFilterInputMethodServices_stillLoadsEnabledImesBeyondThreshold() {
+        final ResolveInfo enabledIme = createFakeResolveInfo(
+                "com.android.apps.inputmethod.simpleime_enabled", "EnabledIME");
+
+        List<ResolveInfo> resolveInfoList = new ArrayList<>();
+        for (int i = 0; i < 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE; i++) {
+            resolveInfoList.add(
+                    createFakeResolveInfo("com.android.apps.inputmethod.simpleime", "IME" + i));
+        }
+        resolveInfoList.add(enabledIme);
+
+        final List<InputMethodInfo> methodList = filterInputMethodServices(resolveInfoList,
+                List.of(new ComponentName(enabledIme.serviceInfo.packageName,
+                        enabledIme.serviceInfo.name).flattenToShortString()));
+
+        assertWithMessage("Filtered IMEs").that(methodList.size()).isEqualTo(
+                1 + InputMethodInfo.MAX_IMES_PER_PACKAGE);
+    }
+
+    private List<InputMethodInfo> filterInputMethodServices(List<ResolveInfo> resolveInfoList,
+            List<String> enabledComponents) {
+        final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+        final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
+        InputMethodManagerService.filterInputMethodServices(new ArrayMap<>(), methodMap, methodList,
+                enabledComponents, mContext, resolveInfoList);
+        return methodList;
+    }
+
+    private ResolveInfo createFakeSystemResolveInfo(String packageName, String componentName) {
+        final ResolveInfo ime = createFakeResolveInfo(packageName, componentName);
+        ime.serviceInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+        return ime;
+    }
+
+    private ResolveInfo createFakeResolveInfo(String packageName, String componentName) {
+        final ResolveInfo ime = getResolveInfo("com.android.apps.inputmethod.simpleime");
+        if (packageName != null) {
+            ime.serviceInfo.packageName = packageName;
+        }
+        if (componentName != null) {
+            ime.serviceInfo.name = componentName;
+        }
+        return ime;
+    }
+
+    private ResolveInfo getResolveInfo(String packageName) {
+        final int flags = PackageManager.GET_META_DATA
+                | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+        final List<ResolveInfo> ime = mContext.getPackageManager().queryIntentServices(
+                new Intent(InputMethod.SERVICE_INTERFACE).setPackage(packageName),
+                PackageManager.ResolveInfoFlags.of(flags));
+        assertWithMessage("Loaded IMEs").that(ime.size()).isGreaterThan(0);
+        return ime.get(0);
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index dbdffd0..9501b96 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -127,7 +127,7 @@
                 mockitoSession()
                         .initMocks(this)
                         .strictness(Strictness.LENIENT)
-                        .mockStatic(LocalServices.class)
+                        .spyStatic(LocalServices.class)
                         .mockStatic(ServiceManager.class)
                         .mockStatic(SystemServerInitThreadPool.class)
                         .startMocking();
@@ -212,6 +212,7 @@
                 new InputMethodManagerService.Lifecycle(mContext, mInputMethodManagerService);
 
         // Public local InputMethodManagerService.
+        LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
         lifecycle.onStart();
         try {
             // After this boot phase, services can broadcast Intents.
@@ -237,6 +238,7 @@
         if (mMockingSession != null) {
             mMockingSession.finishMocking();
         }
+        LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
     }
 
     protected void verifyShowSoftInput(boolean setVisible, boolean showSoftInput)
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/SwitchKeyboardLayoutTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/SwitchKeyboardLayoutTest.java
new file mode 100644
index 0000000..111cabd
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/SwitchKeyboardLayoutTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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.inputmethod;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SwitchKeyboardLayoutTest extends InputMethodManagerServiceTestBase {
+    @Test
+    public void testSwitchToNextKeyboardLayout() {
+        ExtendedMockito.spyOn(mInputMethodManagerService.mSwitchingController);
+        InputMethodManagerInternal.get().switchKeyboardLayout(1);
+        verify(mInputMethodManagerService.mSwitchingController)
+                .getNextInputMethodLocked(eq(true) /* onlyCurrentIme */, any(), any());
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index e711cab..c76af47 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -43,7 +43,6 @@
         "ShortcutManagerTestUtils",
         "truth-prebuilt",
         "testables",
-        "ub-uiautomator",
         "platformprotosnano",
         "framework-protos",
         "hamcrest-library",
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 95c2ed2..99da415 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -759,40 +759,6 @@
     }
 
     /**
-     * Verify that sending a broadcast that removes any matching pending
-     * broadcasts is applied as expected.
-     */
-    @Test
-    public void testRemoveMatchingFilter() {
-        final Intent screenOn = new Intent(Intent.ACTION_SCREEN_ON);
-        final BroadcastOptions optionsOn = BroadcastOptions.makeBasic();
-        optionsOn.setRemoveMatchingFilter(new IntentFilter(Intent.ACTION_SCREEN_OFF));
-
-        final Intent screenOff = new Intent(Intent.ACTION_SCREEN_OFF);
-        final BroadcastOptions optionsOff = BroadcastOptions.makeBasic();
-        optionsOff.setRemoveMatchingFilter(new IntentFilter(Intent.ACTION_SCREEN_ON));
-
-        // Halt all processing so that we get a consistent view
-        mHandlerThread.getLooper().getQueue().postSyncBarrier();
-
-        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, optionsOn));
-        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, optionsOff));
-        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, optionsOn));
-        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, optionsOff));
-
-        // While we're here, give our health check some test coverage
-        mImpl.checkHealthLocked();
-
-        // Marching through the queue we should only have one SCREEN_OFF
-        // broadcast, since that's the last state we dispatched
-        final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
-                getUidForPackage(PACKAGE_GREEN));
-        queue.makeActiveNextPending();
-        assertEquals(Intent.ACTION_SCREEN_OFF, queue.getActive().intent.getAction());
-        assertTrue(queue.isEmpty());
-    }
-
-    /**
      * Verify that sending a broadcast with DELIVERY_GROUP_POLICY_MOST_RECENT works as expected.
      */
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeEmergencyHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeEmergencyHelper.java
index 2cf57da..7ee411b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeEmergencyHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeEmergencyHelper.java
@@ -27,6 +27,7 @@
 
     public void setInEmergency(boolean inEmergency) {
         mInEmergency = inEmergency;
+        dispatchEmergencyStateChanged();
     }
 
     @Override
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index 1298e7b..b89568b 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -18,6 +18,7 @@
 
 import static android.database.sqlite.SQLiteDatabase.deleteDatabase;
 
+import static org.mockito.ArgumentMatchers.contains;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
@@ -707,6 +708,41 @@
     }
 
     @SmallTest
+    public void testStartAddAccountSessionWhereAuthenticatorReturnsIntentWithProhibitedFlags()
+            throws Exception {
+        unlockSystemUser();
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.activityInfo = new ActivityInfo();
+        resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+        when(mMockPackageManager.resolveActivityAsUser(
+                any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+        when(mMockPackageManager.checkSignatures(
+                anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+        Bundle options = createOptionsWithAccountName(
+                AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE);
+        int prohibitedFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+                | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+        options.putInt(AccountManagerServiceTestFixtures.KEY_INTENT_FLAGS, prohibitedFlags);
+
+        mAms.startAddAccountSession(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+                "authTokenType",
+                null, // requiredFeatures
+                true, // expectActivityLaunch
+                options); // optionsIn
+        waitForLatch(latch);
+
+        verify(mMockAccountManagerResponse).onError(
+                eq(AccountManager.ERROR_CODE_INVALID_RESPONSE), contains("invalid intent"));
+    }
+
+    @SmallTest
     public void testStartAddAccountSessionError() throws Exception {
         unlockSystemUser();
         Bundle options = createOptionsWithAccountName(
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java
index 73f30d9..b98a6a8 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java
@@ -17,9 +17,6 @@
 
 import android.accounts.Account;
 
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * Constants shared between test AccountAuthenticators and AccountManagerServiceTest.
  */
@@ -31,6 +28,8 @@
             "account_manager_service_test:account_status_token_key";
     public static final String KEY_ACCOUNT_PASSWORD =
             "account_manager_service_test:account_password_key";
+    public static final String KEY_INTENT_FLAGS =
+            "account_manager_service_test:intent_flags_key";
     public static final String KEY_OPTIONS_BUNDLE =
             "account_manager_service_test:option_bundle_key";
     public static final String ACCOUNT_NAME_SUCCESS = "success_on_return@fixture.com";
diff --git a/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java b/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java
index 8106364..924443e 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java
@@ -24,8 +24,6 @@
 import android.content.Intent;
 import android.os.Bundle;
 
-import com.android.frameworks.servicestests.R;
-
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -270,11 +268,13 @@
         String accountName = null;
         Bundle sessionBundle = null;
         String password = null;
+        int intentFlags = 0;
         if (options != null) {
             accountName = options.getString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_NAME);
             sessionBundle = options.getBundle(
                     AccountManagerServiceTestFixtures.KEY_ACCOUNT_SESSION_BUNDLE);
             password = options.getString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_PASSWORD);
+            intentFlags = options.getInt(AccountManagerServiceTestFixtures.KEY_INTENT_FLAGS, 0);
         }
 
         Bundle result = new Bundle();
@@ -302,6 +302,7 @@
             intent.putExtra(AccountManagerServiceTestFixtures.KEY_RESULT,
                     eventualActivityResultData);
             intent.putExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK, response);
+            intent.setFlags(intentFlags);
 
             result.putParcelable(AccountManager.KEY_INTENT, intent);
         } else {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index cf650d2..e9d8269 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -40,7 +40,9 @@
 import android.hardware.biometrics.AuthenticateOptions;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricService;
+import android.hardware.fingerprint.Fingerprint;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -49,6 +51,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
 import android.testing.TestableLooper;
+import android.util.Slog;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -59,6 +62,8 @@
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.nano.BiometricSchedulerProto;
 import com.android.server.biometrics.nano.BiometricsProto;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -68,6 +73,9 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.function.Supplier;
 
 @Presubmit
@@ -79,6 +87,9 @@
     private static final String TAG = "BiometricSchedulerTest";
     private static final int TEST_SENSOR_ID = 1;
     private static final int LOG_NUM_RECENT_OPERATIONS = 2;
+    private static final Fingerprint TEST_FINGERPRINT = new Fingerprint("" /* name */,
+            1 /* fingerId */, TEST_SENSOR_ID);
+
     @Rule
     public final TestableContext mContext = new TestableContext(
             InstrumentationRegistry.getContext(), null);
@@ -674,6 +685,56 @@
 
     }
 
+    @Test
+    public void testTwoInternalCleanupOps_withFirstFavorHalEnrollment() throws Exception {
+        final String owner = "test.owner";
+        final int userId = 1;
+        final Supplier<Object> daemon = () -> mock(AidlSession.class);
+        final FingerprintUtils utils = mock(FingerprintUtils.class);
+        final Map<Integer, Long> authenticatorIds = new HashMap<>();
+        final ClientMonitorCallback callback0 = mock(ClientMonitorCallback.class);
+        final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
+        final ClientMonitorCallback callback2 = mock(ClientMonitorCallback.class);
+
+        final TestInternalCleanupClient client1 = new
+                TestInternalCleanupClient(mContext, daemon, userId,
+                owner, TEST_SENSOR_ID, mock(BiometricLogger.class),
+                mBiometricContext, utils, authenticatorIds);
+        final TestInternalCleanupClient client2 = new
+                TestInternalCleanupClient(mContext, daemon, userId,
+                owner, TEST_SENSOR_ID, mock(BiometricLogger.class),
+                mBiometricContext, utils, authenticatorIds);
+
+        //add initial start client to scheduler, so later clients will be on pending operation queue
+        final TestHalClientMonitor startClient = new TestHalClientMonitor(mContext, mToken,
+                daemon);
+        mScheduler.scheduleClientMonitor(startClient, callback0);
+
+        //add first cleanup client which favors enrollments from HAL
+        client1.setFavorHalEnrollments();
+        mScheduler.scheduleClientMonitor(client1, callback1);
+        assertEquals(1, mScheduler.mPendingOperations.size());
+
+        when(utils.getBiometricsForUser(mContext, userId)).thenAnswer(i ->
+                new ArrayList<>(client1.getFingerprints()));
+
+        //add second cleanup client
+        mScheduler.scheduleClientMonitor(client2, callback2);
+
+        //finish the start client, so other pending clients are processed
+        startClient.getCallback().onClientFinished(startClient, true);
+
+        waitForIdle();
+
+        assertTrue(client1.isAlreadyDone());
+        assertTrue(client2.isAlreadyDone());
+        assertNull(mScheduler.mCurrentOperation);
+        verify(utils, never()).removeBiometricForUser(mContext, userId,
+                TEST_FINGERPRINT.getBiometricId());
+        assertEquals(1,  client1.getFingerprints().size());
+    }
+
+
     private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
         return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer));
     }
@@ -867,4 +928,90 @@
             mDestroyed = true;
         }
     }
+
+    private static class TestInternalEnumerateClient extends InternalEnumerateClient<Object> {
+        private static final String TAG = "TestInternalEnumerateClient";
+
+
+        protected TestInternalEnumerateClient(@NonNull Context context,
+                @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token, int userId,
+                @NonNull String owner, @NonNull List<Fingerprint> enrolledList,
+                @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+                @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+            super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
+                    logger, biometricContext);
+        }
+
+        @Override
+        protected void startHalOperation() {
+            Slog.d(TAG, "TestInternalEnumerateClient#startHalOperation");
+            onEnumerationResult(TEST_FINGERPRINT, 0 /* remaining */);
+        }
+    }
+
+    private static class TestRemovalClient extends RemovalClient<Fingerprint, Object> {
+        private static final String TAG = "TestRemovalClient";
+
+        TestRemovalClient(@NonNull Context context,
+                @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
+                @Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId,
+                @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+                @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+                @NonNull Map<Integer, Long> authenticatorIds) {
+            super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
+                    logger, biometricContext, authenticatorIds);
+        }
+
+        @Override
+        protected void startHalOperation() {
+            Slog.d(TAG, "Removing template from hw");
+            onRemoved(TEST_FINGERPRINT, 0);
+        }
+    }
+
+    private static class TestInternalCleanupClient extends
+            InternalCleanupClient<Fingerprint, Object> {
+        private List<Fingerprint> mFingerprints = new ArrayList<>();
+
+        TestInternalCleanupClient(@NonNull Context context,
+                @NonNull Supplier<Object> lazyDaemon,
+                int userId, @NonNull String owner, int sensorId,
+                @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+                @NonNull FingerprintUtils utils, @NonNull Map<Integer, Long> authenticatorIds) {
+            super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
+                    utils, authenticatorIds);
+        }
+
+        @Override
+        protected InternalEnumerateClient<Object> getEnumerateClient(Context context,
+                Supplier<Object> lazyDaemon, IBinder token, int userId, String owner,
+                List<Fingerprint> enrolledList, BiometricUtils<Fingerprint> utils, int sensorId,
+                @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+            return new TestInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
+                    enrolledList, utils, sensorId,
+                    logger.swapAction(context, BiometricsProtoEnums.ACTION_ENUMERATE),
+                    biometricContext);
+        }
+
+        @Override
+        protected RemovalClient<Fingerprint, Object> getRemovalClient(Context context,
+                Supplier<Object> lazyDaemon, IBinder token, int biometricId, int userId,
+                String owner, BiometricUtils<Fingerprint> utils, int sensorId,
+                @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+                Map<Integer, Long> authenticatorIds) {
+            return new TestRemovalClient(context, lazyDaemon, token, null,
+                    new int[]{biometricId}, userId, owner, utils, sensorId, logger,
+                    biometricContext, authenticatorIds);
+        }
+
+        @Override
+        protected void onAddUnknownTemplate(int userId,
+                @NonNull BiometricAuthenticator.Identifier identifier) {
+            mFingerprints.add((Fingerprint) identifier);
+        }
+
+        public List<Fingerprint> getFingerprints() {
+            return mFingerprints;
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
index f0d8616..5806443 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
@@ -164,10 +164,9 @@
     }
 
     protected FingerprintInternalCleanupClient createClient() {
-        final List<Fingerprint> enrollments = new ArrayList<>();
         final Map<Integer, Long> authenticatorIds = new HashMap<>();
         return new FingerprintInternalCleanupClient(mContext, () -> mAidlSession, 2 /* userId */,
-                "the.test.owner", SENSOR_ID, mLogger, mBiometricContext, enrollments,
+                "the.test.owner", SENSOR_ID, mLogger, mBiometricContext,
                 mFingerprintUtils, authenticatorIds) {
             @Override
             protected void onAddUnknownTemplate(int userId,
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 5226a3c..8f4c81f 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -16,11 +16,11 @@
 
 package com.android.server.companion.virtual;
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_INVALID;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
+import static android.content.Context.DEVICE_ID_DEFAULT;
+import static android.content.Context.DEVICE_ID_INVALID;
 import static android.content.Intent.ACTION_VIEW;
 import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
 
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
index bb28a36..a2e204d 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
@@ -16,8 +16,8 @@
 
 package com.android.server.companion.virtual;
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_INVALID;
+import static android.content.Context.DEVICE_ID_DEFAULT;
+import static android.content.Context.DEVICE_ID_INVALID;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index 9686c38..1c33d0d 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -165,10 +165,11 @@
     }
 
     @Override
-    protected void tieProfileLockToParent(int userId, LockscreenCredential password) {
+    protected void tieProfileLockToParent(int profileUserId, int parentUserId,
+            LockscreenCredential password) {
         Parcel parcel = Parcel.obtain();
         parcel.writeParcelable(password, 0);
-        mStorage.writeChildProfileLock(userId, parcel.marshall());
+        mStorage.writeChildProfileLock(profileUserId, parcel.marshall());
         parcel.recycle();
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 5cdaf0d..b693974 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -67,6 +67,7 @@
 import android.os.Vibrator;
 import android.service.dreams.DreamManagerInternal;
 import android.telecom.TelecomManager;
+import android.util.FeatureFlagUtils;
 import android.view.Display;
 import android.view.KeyEvent;
 import android.view.autofill.AutofillManagerInternal;
@@ -76,6 +77,7 @@
 import com.android.server.GestureLauncherService;
 import com.android.server.LocalServices;
 import com.android.server.input.InputManagerInternal;
+import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.vr.VrManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -114,6 +116,7 @@
     @Mock private Vibrator mVibrator;
     @Mock private PowerManager mPowerManager;
     @Mock private WindowManagerPolicy.WindowManagerFuncs mWindowManagerFuncsImpl;
+    @Mock private InputMethodManagerInternal mInputMethodManagerInternal;
     @Mock private AudioManagerInternal mAudioManagerInternal;
     @Mock private SearchManager mSearchManager;
 
@@ -184,6 +187,8 @@
                 () -> LocalServices.getService(eq(GestureLauncherService.class)));
         doReturn(null).when(() -> LocalServices.getService(eq(VrManagerInternal.class)));
         doReturn(null).when(() -> LocalServices.getService(eq(AutofillManagerInternal.class)));
+        LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
+        LocalServices.addService(InputMethodManagerInternal.class, mInputMethodManagerInternal);
 
         doReturn(mAppOpsManager).when(mContext).getSystemService(eq(AppOpsManager.class));
         doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class));
@@ -242,6 +247,7 @@
 
     void tearDown() {
         mHandlerThread.quitSafely();
+        LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
         mMockitoSession.finishMocking();
     }
 
@@ -417,7 +423,13 @@
 
     void assertSwitchKeyboardLayout(int direction) {
         waitForIdle();
-        verify(mWindowManagerFuncsImpl).switchKeyboardLayout(anyInt(), eq(direction));
+        if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
+            verify(mInputMethodManagerInternal).switchKeyboardLayout(eq(direction));
+            verify(mWindowManagerFuncsImpl, never()).switchKeyboardLayout(anyInt(), anyInt());
+        } else {
+            verify(mWindowManagerFuncsImpl).switchKeyboardLayout(anyInt(), eq(direction));
+            verify(mInputMethodManagerInternal, never()).switchKeyboardLayout(anyInt());
+        }
     }
 
     void assertTakeBugreport() {
diff --git a/telephony/java/android/telephony/DomainSelectionService.java b/telephony/java/android/telephony/DomainSelectionService.java
index c352f2b..abcce5f 100644
--- a/telephony/java/android/telephony/DomainSelectionService.java
+++ b/telephony/java/android/telephony/DomainSelectionService.java
@@ -537,9 +537,9 @@
         }
 
         @Override
-        public void onWlanSelected() {
+        public void onWlanSelected(boolean useEmergencyPdn) {
             try {
-                mCallback.onWlanSelected();
+                mCallback.onWlanSelected(useEmergencyPdn);
             } catch (Exception e) {
                 Rlog.e(TAG, "onWlanSelected e=" + e);
             }
@@ -702,9 +702,10 @@
         }
 
         @Override
-        public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain) {
+        public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain,
+                boolean useEmergencyPdn) {
             try {
-                mCallback.onDomainSelected(domain);
+                mCallback.onDomainSelected(domain, useEmergencyPdn);
             } catch (Exception e) {
                 Rlog.e(TAG, "onDomainSelected e=" + e);
             }
diff --git a/telephony/java/android/telephony/TransportSelectorCallback.java b/telephony/java/android/telephony/TransportSelectorCallback.java
index d396790..04752e4 100644
--- a/telephony/java/android/telephony/TransportSelectorCallback.java
+++ b/telephony/java/android/telephony/TransportSelectorCallback.java
@@ -35,8 +35,10 @@
 
     /**
      * Notify that WLAN transport has been selected.
+     *
+     * @param useEmergencyPdn Indicates whether Wi-Fi emergency services use emergency PDN or not.
      */
-    void onWlanSelected();
+    void onWlanSelected(boolean useEmergencyPdn);
 
     /**
      * Notify that WWAN transport has been selected.
diff --git a/telephony/java/android/telephony/WwanSelectorCallback.java b/telephony/java/android/telephony/WwanSelectorCallback.java
index b3682ca..f9c2620 100644
--- a/telephony/java/android/telephony/WwanSelectorCallback.java
+++ b/telephony/java/android/telephony/WwanSelectorCallback.java
@@ -46,6 +46,7 @@
      * this interface can be discarded.
      *
      * @param domain The selected domain.
+     * @param useEmergencyPdn Indicates whether emergency services use emergency PDN or not.
      */
-    void onDomainSelected(@NetworkRegistrationInfo.Domain int domain);
+    void onDomainSelected(@NetworkRegistrationInfo.Domain int domain, boolean useEmergencyPdn);
 }
diff --git a/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl b/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl
index aca83f4..6777256d 100644
--- a/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl
+++ b/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl
@@ -22,7 +22,7 @@
 
 interface ITransportSelectorCallback {
     oneway void onCreated(in IDomainSelector selector);
-    oneway void onWlanSelected();
+    oneway void onWlanSelected(boolean useEmergencyPdn);
     IWwanSelectorCallback onWwanSelected();
     oneway void onWwanSelectedAsync(in ITransportSelectorResultCallback cb);
     oneway void onSelectionTerminated(int cause);
diff --git a/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
index 339fbee..94e7c87 100644
--- a/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
+++ b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
@@ -21,6 +21,6 @@
 oneway interface IWwanSelectorCallback {
     void onRequestEmergencyNetworkScan(in int[] preferredNetworks,
             int scanType, in IWwanSelectorResultCallback cb);
-    void onDomainSelected(int domain);
+    void onDomainSelected(int domain, boolean useEmergencyPdn);
     void onCancel();
 }