Merge "Fix back gesture send to wrong focus window" into tm-dev
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 974d20a..e820733 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -7770,10 +7770,11 @@
      * user will always see the normal notification view.
      *
      * <p>
-     * If the app is targeting Android P and above, it is required to use the {@link Person}
-     * class in order to get an optimal rendering of the notification and its avatars. For
-     * conversations involving multiple people, the app should also make sure that it marks the
-     * conversation as a group with {@link #setGroupConversation(boolean)}.
+     * If the app is targeting Android {@link android.os.Build.VERSION_CODES#P} and above, it is
+     * required to use the {@link Person} class in order to get an optimal rendering of the
+     * notification and its avatars. For conversations involving multiple people, the app should
+     * also make sure that it marks the conversation as a group with
+     * {@link #setGroupConversation(boolean)}.
      *
      * <p>
      * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior.
@@ -7846,8 +7847,8 @@
          * @param user Required - The person displayed for any messages that are sent by the
          * user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)}
          * who don't have a Person associated with it will be displayed as if they were sent
-         * by this user. The user also needs to have a valid name associated with it, which will
-         * be enforced starting in Android P.
+         * by this user. The user also needs to have a valid name associated with it, which is
+         * enforced starting in Android {@link android.os.Build.VERSION_CODES#P}.
          */
         public MessagingStyle(@NonNull Person user) {
             mUser = user;
@@ -7904,9 +7905,9 @@
         /**
          * Sets the title to be displayed on this conversation. May be set to {@code null}.
          *
-         * <p>Starting in {@link Build.VERSION_CODES#R, this conversation title will be ignored if a
-         * valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}. In this
-         * case, {@link ShortcutInfo#getLongLabel()} (or, if missing,
+         * <p>Starting in {@link Build.VERSION_CODES#R}, this conversation title will be ignored
+         * if a valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}.
+         * In this case, {@link ShortcutInfo#getLongLabel()} (or, if missing,
          * {@link ShortcutInfo#getShortLabel()}) will be shown as the conversation title
          * instead.
          *
@@ -8065,7 +8066,7 @@
         }
 
         /**
-         * Gets the list of {@code Message} objects that represent the notification
+         * Gets the list of {@code Message} objects that represent the notification.
          */
         public List<Message> getMessages() {
             return mMessages;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 315bd71..0a2b421 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1811,10 +1811,6 @@
      * #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra contain {@link
      * #PROVISIONING_MODE_MANAGED_PROFILE} and {@link #PROVISIONING_MODE_FULLY_MANAGED_DEVICE}.
      *
-     * <p>Also, if this flag is set, the admin app's {@link #ACTION_GET_PROVISIONING_MODE} activity
-     * will not receive the {@link #EXTRA_PROVISIONING_IMEI} and {@link
-     * #EXTRA_PROVISIONING_SERIAL_NUMBER} extras.
-     *
      * <p>This flag can be combined with {@link #FLAG_SUPPORTED_MODES_PERSONALLY_OWNED}. In
      * that case, the admin app's {@link #ACTION_GET_PROVISIONING_MODE} activity will have
      * the {@link #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra contain {@link
@@ -1834,6 +1830,10 @@
      * activity to have the {@link #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra
      * contain only {@link #PROVISIONING_MODE_MANAGED_PROFILE}.
      *
+     * <p>Also, if this flag is set, the admin app's {@link #ACTION_GET_PROVISIONING_MODE} activity
+     * will not receive the {@link #EXTRA_PROVISIONING_IMEI} and {@link
+     * #EXTRA_PROVISIONING_SERIAL_NUMBER} extras.
+     *
      * <p>This flag can be combined with {@link #FLAG_SUPPORTED_MODES_ORGANIZATION_OWNED}. In
      * that case, the admin app's {@link #ACTION_GET_PROVISIONING_MODE} activity will have the
      * {@link #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra contain {@link
@@ -13249,8 +13249,9 @@
      * Called by a device owner, profile owner of a managed profile or delegated app with
      * {@link #DELEGATION_NETWORK_LOGGING} to control the network logging feature.
      *
-     * <p> When network logging is enabled by a profile owner, the network logs will only include
-     * work profile network activity, not activity on the personal profile.
+     * <p> Supported for a device owner from Android 8. Supported for a profile owner of a managed
+     * profile from Android 12. When network logging is enabled by a profile owner, the network logs
+     * will only include work profile network activity, not activity on the personal profile.
      *
      * <p> Network logs contain DNS lookup and connect() library call events. The following library
      *     functions are recorded while network logging is active:
diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl
index a392afd..9cf329c 100644
--- a/core/java/android/hardware/ISensorPrivacyManager.aidl
+++ b/core/java/android/hardware/ISensorPrivacyManager.aidl
@@ -50,5 +50,7 @@
     void suppressToggleSensorPrivacyReminders(int userId, int sensor, IBinder token,
             boolean suppress);
 
+    boolean requiresAuthentication();
+
     void showSensorUseDialog(int sensor);
 }
\ No newline at end of file
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 0460e58..99b58c9 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -327,6 +327,8 @@
     @NonNull
     private boolean mToggleListenerRegistered = false;
 
+    private Boolean mRequiresAuthentication = null;
+
     /**
      * Private constructor to ensure only a single instance is created.
      */
@@ -761,6 +763,23 @@
     }
 
     /**
+     * @return whether the device is required to be unlocked to change software state.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
+    public boolean requiresAuthentication() {
+        if (mRequiresAuthentication == null) {
+            try {
+                mRequiresAuthentication = mService.requiresAuthentication();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return mRequiresAuthentication;
+    }
+
+    /**
      * If sensor privacy for the provided sensor is enabled then this call will show the user the
      * dialog which is shown when an application attempts to use that sensor. If privacy isn't
      * enabled then this does nothing.
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 1263da6..4d0ba63 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -72,7 +72,7 @@
 import java.util.concurrent.Executor;
 
 public final class CameraExtensionSessionImpl extends CameraExtensionSession {
-    private static final int PREVIEW_QUEUE_SIZE = 3;
+    private static final int PREVIEW_QUEUE_SIZE = 10;
     private static final String TAG = "CameraExtensionSessionImpl";
 
     private final Executor mExecutor;
@@ -1057,15 +1057,8 @@
                                                 mClientRequest));
 
                         if (mCaptureResultHandler != null) {
-                            CameraMetadataNative captureResults = new CameraMetadataNative();
-                            for (CaptureResult.Key key : mSupportedResultKeys) {
-                                Object value = result.get(key);
-                                if (value != null) {
-                                    captureResults.set(key, value);
-                                }
-                            }
                             mCaptureResultHandler.onCaptureCompleted(timestamp,
-                                    captureResults);
+                                    initializeFilteredResults(result));
                         }
                     } finally {
                         Binder.restoreCallingIdentity(ident);
@@ -1127,6 +1120,11 @@
 
         private class ImageCallback implements OnImageAvailableListener {
             @Override
+            public void onImageDropped(long timestamp) {
+                notifyCaptureFailed();
+            }
+
+            @Override
             public void onImageAvailable(ImageReader reader, Image img) {
                 if (mCaptureFailed) {
                     img.close();
@@ -1160,6 +1158,9 @@
 
     private class ImageLoopbackCallback implements OnImageAvailableListener {
         @Override
+        public void onImageDropped(long timestamp) { }
+
+        @Override
         public void onImageAvailable(ImageReader reader, Image img) {
             img.close();
         }
@@ -1221,7 +1222,8 @@
     }
 
     private interface OnImageAvailableListener {
-        public void onImageAvailable (ImageReader reader, Image img);
+        void onImageDropped(long timestamp);
+        void onImageAvailable (ImageReader reader, Image img);
     }
 
     private class CameraOutputImageCallback implements ImageReader.OnImageAvailableListener,
@@ -1263,6 +1265,29 @@
             } else {
                 mImageListenerMap.put(img.getTimestamp(), new Pair<>(img, null));
             }
+
+            notifyDroppedImages(timestamp);
+        }
+
+        private void notifyDroppedImages(long timestamp) {
+            Set<Long> timestamps = mImageListenerMap.keySet();
+            ArrayList<Long> removedTs = new ArrayList<>();
+            for (long ts : timestamps) {
+                if (ts < timestamp) {
+                    Log.e(TAG, "Dropped image with ts: " + ts);
+                    Pair<Image, OnImageAvailableListener> entry = mImageListenerMap.get(ts);
+                    if (entry.second != null) {
+                        entry.second.onImageDropped(ts);
+                    }
+                    if (entry.first != null) {
+                        entry.first.close();
+                    }
+                    removedTs.add(ts);
+                }
+            }
+            for (long ts : removedTs) {
+                mImageListenerMap.remove(ts);
+            }
         }
 
         public void registerListener(Long timestamp, OnImageAvailableListener listener) {
@@ -1291,6 +1316,12 @@
                     entry.first.close();
                 }
             }
+            for (long timestamp : mImageListenerMap.keySet()) {
+                Pair<Image, OnImageAvailableListener> entry = mImageListenerMap.get(timestamp);
+                if (entry.second != null) {
+                    entry.second.onImageDropped(timestamp);
+                }
+            }
             mImageListenerMap.clear();
         }
     }
@@ -1447,7 +1478,6 @@
             } else {
                 notifyConfigurationFailure();
             }
-
         }
 
         @Override
@@ -1536,7 +1566,16 @@
                     } else if (mPreviewProcessorType ==
                             IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR) {
                         int idx = mPendingResultMap.indexOfKey(timestamp);
-                        if (idx >= 0) {
+
+                        if ((idx >= 0) && (mPendingResultMap.get(timestamp).first == null)) {
+                            // Image was dropped before we can receive the capture results
+                            if ((mCaptureResultHandler != null)) {
+                                mCaptureResultHandler.onCaptureCompleted(timestamp,
+                                        initializeFilteredResults(result));
+                            }
+                            discardPendingRepeatingResults(idx, mPendingResultMap, false);
+                        } else  if (idx >= 0) {
+                            // Image came before the capture results
                             ParcelImage parcelImage = initializeParcelImage(
                                     mPendingResultMap.get(timestamp).first);
                             try {
@@ -1563,6 +1602,7 @@
                             }
                             discardPendingRepeatingResults(idx, mPendingResultMap, false);
                         } else {
+                            // Image not yet available
                             notifyClient = false;
                             mPendingResultMap.put(timestamp,
                                     new Pair<>(null,
@@ -1581,16 +1621,8 @@
                                                 mClientRequest));
                                 if ((mCaptureResultHandler != null) && (mPreviewProcessorType !=
                                         IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR)) {
-                                    CameraMetadataNative captureResults =
-                                            new CameraMetadataNative();
-                                    for (CaptureResult.Key key : mSupportedResultKeys) {
-                                        Object value = result.get(key);
-                                        if (value != null) {
-                                            captureResults.set(key, value);
-                                        }
-                                    }
                                     mCaptureResultHandler.onCaptureCompleted(timestamp,
-                                            captureResults);
+                                            initializeFilteredResults(result));
                                 }
                             } else {
                                 mExecutor.execute(
@@ -1657,19 +1689,24 @@
             for (int i = idx; i >= 0; i--) {
                 if (previewMap.valueAt(i).first != null) {
                     previewMap.valueAt(i).first.close();
-                } else {
-                    if (mClientNotificationsEnabled && ((i != idx) || notifyCurrentIndex)) {
-                        Log.w(TAG, "Preview frame drop with timestamp: " + previewMap.keyAt(i));
-                        final long ident = Binder.clearCallingIdentity();
-                        try {
-                            mExecutor.execute(
-                                    () -> mCallbacks
-                                            .onCaptureFailed(CameraExtensionSessionImpl.this,
-                                                    mClientRequest));
-                        } finally {
-                            Binder.restoreCallingIdentity(ident);
-                        }
+                } else if (mClientNotificationsEnabled && (previewMap.valueAt(i).second != null) &&
+                        ((i != idx) || notifyCurrentIndex)) {
+                    TotalCaptureResult result = previewMap.valueAt(i).second;
+                    Long timestamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
+                    mCaptureResultHandler.onCaptureCompleted(timestamp,
+                            initializeFilteredResults(result));
+
+                    Log.w(TAG, "Preview frame drop with timestamp: " + previewMap.keyAt(i));
+                    final long ident = Binder.clearCallingIdentity();
+                    try {
+                        mExecutor.execute(
+                                () -> mCallbacks
+                                        .onCaptureFailed(CameraExtensionSessionImpl.this,
+                                                mClientRequest));
+                    } finally {
+                        Binder.restoreCallingIdentity(ident);
                     }
+
                 }
                 previewMap.removeAt(i);
             }
@@ -1683,6 +1720,12 @@
             }
 
             @Override
+            public void onImageDropped(long timestamp) {
+                discardPendingRepeatingResults(mPendingResultMap.indexOfKey(timestamp),
+                        mPendingResultMap, true);
+            }
+
+            @Override
             public void onImageAvailable(ImageReader reader, Image img) {
                 if (img == null) {
                     Log.e(TAG, "Invalid image!");
@@ -1703,6 +1746,15 @@
         private class ImageProcessCallback implements OnImageAvailableListener {
 
             @Override
+            public void onImageDropped(long timestamp) {
+                discardPendingRepeatingResults(mPendingResultMap.indexOfKey(timestamp),
+                        mPendingResultMap, true);
+                // Add an empty frame&results entry to flag that we dropped a frame
+                // and valid capture results can immediately return to client.
+                mPendingResultMap.put(timestamp, new Pair<>(null, null));
+            }
+
+            @Override
             public void onImageAvailable(ImageReader reader, Image img) {
                 if (mPendingResultMap.size() + 1 >= PREVIEW_QUEUE_SIZE) {
                     // We reached the maximum acquired images limit. This is possible in case we
@@ -1768,6 +1820,17 @@
         }
     }
 
+    private CameraMetadataNative initializeFilteredResults(TotalCaptureResult result) {
+        CameraMetadataNative captureResults = new CameraMetadataNative();
+        for (CaptureResult.Key key : mSupportedResultKeys) {
+            Object value = result.get(key);
+            if (value != null) {
+                captureResults.set(key, value);
+            }
+        }
+        return captureResults;
+    }
+
     private static Size findSmallestAspectMatchedSize(@NonNull List<Size> sizes,
             @NonNull Size arSize) {
         final float TOLL = .01f;
diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java
index b37c27c..fc6bc55 100644
--- a/core/java/android/hardware/input/InputManagerInternal.java
+++ b/core/java/android/hardware/input/InputManagerInternal.java
@@ -75,8 +75,15 @@
     /**
      * Sets the display id that the MouseCursorController will be forced to target. Pass
      * {@link android.view.Display#INVALID_DISPLAY} to clear the override.
+     *
+     * Note: This method generally blocks until the pointer display override has propagated.
+     * When setting a new override, the caller should ensure that an input device that can control
+     * the mouse pointer is connected. If a new override is set when no such input device is
+     * connected, the caller may be blocked for an arbitrary period of time.
+     *
+     * @return true if the pointer displayId was set successfully, or false if it fails.
      */
-    public abstract void setVirtualMousePointerDisplayId(int pointerDisplayId);
+    public abstract boolean setVirtualMousePointerDisplayId(int pointerDisplayId);
 
     /**
      * Gets the display id that the MouseCursorController is being forced to target. Returns
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index ecdc803..022d213 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -1361,6 +1361,18 @@
             return false;
         }
 
+        // Apps with PROPERTY_NO_APP_DATA_STORAGE should not be allowed in scoped storage
+        final String packageName = AppGlobals.getInitialPackage();
+        try {
+            final PackageManager.Property noAppStorageProp = packageManager.getProperty(
+                    PackageManager.PROPERTY_NO_APP_DATA_STORAGE, packageName);
+            if (noAppStorageProp != null && noAppStorageProp.getBoolean()) {
+                return false;
+            }
+        } catch (PackageManager.NameNotFoundException ignore) {
+            // Property not defined for the package
+        }
+
         boolean defaultScopedStorage = Compatibility.isChangeEnabled(DEFAULT_SCOPED_STORAGE);
         boolean forceEnableScopedStorage = Compatibility.isChangeEnabled(
                 FORCE_ENABLE_SCOPED_STORAGE);
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 0e5a65c..77c0067 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -1292,7 +1292,8 @@
                 USER_MISSED_LOW_RING_VOLUME,
                 USER_MISSED_NO_VIBRATE,
                 USER_MISSED_CALL_SCREENING_SERVICE_SILENCED,
-                USER_MISSED_CALL_FILTERS_TIMEOUT
+                USER_MISSED_CALL_FILTERS_TIMEOUT,
+                USER_MISSED_NEVER_RANG
         })
         @Retention(RetentionPolicy.SOURCE)
         public @interface MissedReason {}
@@ -1383,6 +1384,13 @@
         public static final long USER_MISSED_CALL_FILTERS_TIMEOUT = 1 << 22;
 
         /**
+         * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when
+         * the call ended before ringing.
+         * @hide
+         */
+        public static final long USER_MISSED_NEVER_RANG = 1 << 23;
+
+        /**
          * Where the {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE},
          * indicates factors which may have lead the user to miss the call.
          * <P>Type: INTEGER</P>
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 3be4c3ed..24ded93 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -50,12 +50,21 @@
     public static final String SETTINGS_ENABLE_SECURITY_HUB = "settings_enable_security_hub";
     /** @hide */
     public static final String SETTINGS_SUPPORT_LARGE_SCREEN = "settings_support_large_screen";
+
     /**
      * Support per app's language selection
      * @hide
      */
     public static final String SETTINGS_APP_LANGUAGE_SELECTION = "settings_app_language_selection";
 
+    /**
+     * Support locale opt-out and opt-in switch for per app's language.
+     * @hide
+     */
+    public static final String SETTINGS_APP_LOCALE_OPT_IN_ENABLED =
+            "settings_app_locale_opt_in_enabled";
+
+
     /** @hide */
     public static final String SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS =
             "settings_enable_monitor_phantom_procs";
@@ -97,6 +106,7 @@
         DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "true");
         DEFAULT_FLAGS.put("settings_search_always_expand", "true");
         DEFAULT_FLAGS.put(SETTINGS_APP_LANGUAGE_SELECTION, "true");
+        DEFAULT_FLAGS.put(SETTINGS_APP_LOCALE_OPT_IN_ENABLED, "true");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
         DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
         DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true");
@@ -106,6 +116,7 @@
     static {
         PERSISTENT_FLAGS = new HashSet<>();
         PERSISTENT_FLAGS.add(SETTINGS_APP_LANGUAGE_SELECTION);
+        PERSISTENT_FLAGS.add(SETTINGS_APP_LOCALE_OPT_IN_ENABLED);
         PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN);
         PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
         PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME);
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index fd557e7..2e48c2b 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -719,9 +719,14 @@
 
     private void releaseSurfaces(boolean releaseSurfacePackage) {
         mSurfaceAlpha = 1f;
-
-        synchronized (mSurfaceControlLock) {
+	
+        mSurfaceLock.lock();
+        try {
             mSurface.destroy();
+        } finally {
+            mSurfaceLock.unlock();
+        }
+        synchronized (mSurfaceControlLock) {
             if (mBlastBufferQueue != null) {
                 mBlastBufferQueue.destroy();
                 mBlastBufferQueue = null;
@@ -770,105 +775,99 @@
             Transaction surfaceUpdateTransaction) {
         boolean realSizeChanged = false;
 
-        mSurfaceLock.lock();
-        try {
-            mDrawingStopped = !mVisible;
+        mDrawingStopped = !mVisible;
 
-            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                    + "Cur surface: " + mSurface);
+        if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+                + "Cur surface: " + mSurface);
 
-            // If we are creating the surface control or the parent surface has not
-            // changed, then set relative z. Otherwise allow the parent
-            // SurfaceChangedCallback to update the relative z. This is needed so that
-            // we do not change the relative z before the server is ready to swap the
-            // parent surface.
-            if (creating) {
-                updateRelativeZ(surfaceUpdateTransaction);
-                if (mSurfacePackage != null) {
-                    reparentSurfacePackage(surfaceUpdateTransaction, mSurfacePackage);
-                }
+        // If we are creating the surface control or the parent surface has not
+        // changed, then set relative z. Otherwise allow the parent
+        // SurfaceChangedCallback to update the relative z. This is needed so that
+        // we do not change the relative z before the server is ready to swap the
+        // parent surface.
+        if (creating) {
+            updateRelativeZ(surfaceUpdateTransaction);
+            if (mSurfacePackage != null) {
+                reparentSurfacePackage(surfaceUpdateTransaction, mSurfacePackage);
             }
-            mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId();
-
-            if (mViewVisibility) {
-                surfaceUpdateTransaction.show(mSurfaceControl);
-            } else {
-                surfaceUpdateTransaction.hide(mSurfaceControl);
-            }
-
-
-
-            updateBackgroundVisibility(surfaceUpdateTransaction);
-            updateBackgroundColor(surfaceUpdateTransaction);
-            if (mUseAlpha) {
-                float alpha = getFixedAlpha();
-                surfaceUpdateTransaction.setAlpha(mSurfaceControl, alpha);
-                mSurfaceAlpha = alpha;
-            }
-
-            surfaceUpdateTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
-            if ((sizeChanged || hintChanged) && !creating) {
-                setBufferSize(surfaceUpdateTransaction);
-            }
-            if (sizeChanged || creating || !isHardwareAccelerated()) {
-
-                // Set a window crop when creating the surface or changing its size to
-                // crop the buffer to the surface size since the buffer producer may
-                // use SCALING_MODE_SCALE and submit a larger size than the surface
-                // size.
-                if (mClipSurfaceToBounds && mClipBounds != null) {
-                    surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mClipBounds);
-                } else {
-                    surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth,
-                            mSurfaceHeight);
-                }
-
-                surfaceUpdateTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth,
-                            mSurfaceHeight);
-
-                if (isHardwareAccelerated()) {
-                    // This will consume the passed in transaction and the transaction will be
-                    // applied on a render worker thread.
-                    replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight);
-                } else {
-                    onSetSurfacePositionAndScale(surfaceUpdateTransaction, mSurfaceControl,
-                            mScreenRect.left /*positionLeft*/,
-                            mScreenRect.top /*positionTop*/,
-                            mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
-                            mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
-                }
-                if (DEBUG_POSITION) {
-                    Log.d(TAG, String.format(
-                            "%d performSurfaceTransaction %s "
-                                + "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
-                            System.identityHashCode(this),
-                            isHardwareAccelerated() ? "RenderWorker" : "UI Thread",
-                            mScreenRect.left, mScreenRect.top, mScreenRect.right,
-                            mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight));
-                }
-            }
-            applyTransactionOnVriDraw(surfaceUpdateTransaction);
-            updateEmbeddedAccessibilityMatrix(false);
-
-            mSurfaceFrame.left = 0;
-            mSurfaceFrame.top = 0;
-            if (translator == null) {
-                mSurfaceFrame.right = mSurfaceWidth;
-                mSurfaceFrame.bottom = mSurfaceHeight;
-            } else {
-                float appInvertedScale = translator.applicationInvertedScale;
-                mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
-                mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
-            }
-            final int surfaceWidth = mSurfaceFrame.right;
-            final int surfaceHeight = mSurfaceFrame.bottom;
-            realSizeChanged = mLastSurfaceWidth != surfaceWidth
-                    || mLastSurfaceHeight != surfaceHeight;
-            mLastSurfaceWidth = surfaceWidth;
-            mLastSurfaceHeight = surfaceHeight;
-        } finally {
-            mSurfaceLock.unlock();
         }
+        mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId();
+
+        if (mViewVisibility) {
+            surfaceUpdateTransaction.show(mSurfaceControl);
+        } else {
+            surfaceUpdateTransaction.hide(mSurfaceControl);
+        }
+
+
+
+        updateBackgroundVisibility(surfaceUpdateTransaction);
+        updateBackgroundColor(surfaceUpdateTransaction);
+        if (mUseAlpha) {
+            float alpha = getFixedAlpha();
+            surfaceUpdateTransaction.setAlpha(mSurfaceControl, alpha);
+            mSurfaceAlpha = alpha;
+        }
+
+        surfaceUpdateTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
+        if ((sizeChanged || hintChanged) && !creating) {
+            setBufferSize(surfaceUpdateTransaction);
+        }
+        if (sizeChanged || creating || !isHardwareAccelerated()) {
+            // Set a window crop when creating the surface or changing its size to
+            // crop the buffer to the surface size since the buffer producer may
+            // use SCALING_MODE_SCALE and submit a larger size than the surface
+            // size.
+            if (mClipSurfaceToBounds && mClipBounds != null) {
+                surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mClipBounds);
+            } else {
+                surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth,
+                        mSurfaceHeight);
+            }
+
+            surfaceUpdateTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth,
+                        mSurfaceHeight);
+
+            if (isHardwareAccelerated()) {
+                // This will consume the passed in transaction and the transaction will be
+                // applied on a render worker thread.
+                replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight);
+            } else {
+                onSetSurfacePositionAndScale(surfaceUpdateTransaction, mSurfaceControl,
+                        mScreenRect.left /*positionLeft*/,
+                        mScreenRect.top /*positionTop*/,
+                        mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
+                        mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
+            }
+            if (DEBUG_POSITION) {
+                Log.d(TAG, String.format(
+                        "%d performSurfaceTransaction %s "
+                            + "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
+                        System.identityHashCode(this),
+                        isHardwareAccelerated() ? "RenderWorker" : "UI Thread",
+                        mScreenRect.left, mScreenRect.top, mScreenRect.right,
+                        mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight));
+            }
+        }
+        applyTransactionOnVriDraw(surfaceUpdateTransaction);
+        updateEmbeddedAccessibilityMatrix(false);
+         mSurfaceFrame.left = 0;
+        mSurfaceFrame.top = 0;
+        if (translator == null) {
+            mSurfaceFrame.right = mSurfaceWidth;
+            mSurfaceFrame.bottom = mSurfaceHeight;
+        } else {
+            float appInvertedScale = translator.applicationInvertedScale;
+            mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
+            mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
+        }
+        final int surfaceWidth = mSurfaceFrame.right;
+        final int surfaceHeight = mSurfaceFrame.bottom;
+        realSizeChanged = mLastSurfaceWidth != surfaceWidth
+                || mLastSurfaceHeight != surfaceHeight;
+        mLastSurfaceWidth = surfaceWidth;
+        mLastSurfaceHeight = surfaceHeight;
+
         return realSizeChanged;
     }
 
@@ -1103,21 +1102,30 @@
      *                          Surface for compatibility reasons.
      */
     private void copySurface(boolean surfaceControlCreated, boolean bufferSizeChanged) {
-        if (surfaceControlCreated) {
-            mSurface.copyFrom(mBlastBufferQueue);
-        }
+        // Some legacy applications use the underlying native {@link Surface} object
+        // as a key to whether anything has changed. In these cases, updates to the
+        // existing {@link Surface} will be ignored when the size changes.
+        // Therefore, we must explicitly recreate the {@link Surface} in these
+        // cases.
+        boolean needsWorkaround = bufferSizeChanged &&
+            getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O;
+       if (!surfaceControlCreated && !needsWorkaround) {
+           return;
+       }
+       mSurfaceLock.lock();
+       try {
+           if (surfaceControlCreated) {
+               mSurface.copyFrom(mBlastBufferQueue);
+           }
 
-        if (bufferSizeChanged && getContext().getApplicationInfo().targetSdkVersion
-                < Build.VERSION_CODES.O) {
-            // Some legacy applications use the underlying native {@link Surface} object
-            // as a key to whether anything has changed. In these cases, updates to the
-            // existing {@link Surface} will be ignored when the size changes.
-            // Therefore, we must explicitly recreate the {@link Surface} in these
-            // cases.
-            if (mBlastBufferQueue != null) {
-                mSurface.transferFrom(mBlastBufferQueue.createSurfaceWithHandle());
-            }
-        }
+           if (needsWorkaround) {
+               if (mBlastBufferQueue != null) {
+                   mSurface.transferFrom(mBlastBufferQueue.createSurfaceWithHandle());
+               }
+           }
+       } finally {
+           mSurfaceLock.unlock();
+       }
     }
 
     private void setBufferSize(Transaction transaction) {
@@ -1200,8 +1208,10 @@
         }
         mTransformHint = viewRoot.getBufferTransformHint();
         mBlastSurfaceControl.setTransformHint(mTransformHint);
+
         mBlastBufferQueue = new BLASTBufferQueue(name, false /* updateDestinationFrame */);
         mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, mFormat);
+        mBlastBufferQueue.setTransactionHangCallback(ViewRootImpl.sTransactionHangCallback);
     }
 
     private void onDrawFinished() {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 8901d86..2475e2c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8206,24 +8206,25 @@
         if (canNotifyAutofillEnterExitEvent()) {
             AutofillManager afm = getAutofillManager();
             if (afm != null) {
-                if (enter && isFocused()) {
+                if (enter) {
                     // We have not been laid out yet, hence cannot evaluate
                     // whether this view is visible to the user, we will do
                     // the evaluation once layout is complete.
                     if (!isLaidOut()) {
                         mPrivateFlags3 |= PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
                     } else if (isVisibleToUser()) {
-                        // TODO This is a potential problem that View gets focus before it's visible
-                        // to User. Ideally View should handle the event when isVisibleToUser()
-                        // becomes true where it should issue notifyViewEntered().
-                        afm.notifyViewEntered(this);
-                    } else {
-                        afm.enableFillRequestActivityStarted(this);
+                        if (isFocused()) {
+                            // TODO This is a potential problem that View gets focus before it's
+                            // visible to User. Ideally View should handle the event when
+                            // isVisibleToUser() becomes true where it should issue
+                            // notifyViewEntered().
+                            afm.notifyViewEntered(this);
+                        } else {
+                            afm.notifyViewEnteredForFillDialog(this);
+                        }
                     }
-                } else if (!enter && !isFocused()) {
+                } else if (!isFocused()) {
                     afm.notifyViewExited(this);
-                } else if (enter) {
-                    afm.enableFillRequestActivityStarted(this);
                 }
             }
         }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 42b5691..0bdbfbc 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -590,7 +590,6 @@
     @Nullable
     int mContentCaptureEnabled = CONTENT_CAPTURE_ENABLED_NOT_CHECKED;
     boolean mPerformContentCapture;
-    boolean mPerformAutoFill;
 
 
     boolean mReportNextDraw;
@@ -858,6 +857,28 @@
      */
     private Bundle mRelayoutBundle = new Bundle();
 
+    private static volatile boolean sAnrReported = false;
+    static BLASTBufferQueue.TransactionHangCallback sTransactionHangCallback =
+        new BLASTBufferQueue.TransactionHangCallback() {
+            @Override
+            public void onTransactionHang(boolean isGPUHang) {
+                if (isGPUHang && !sAnrReported) {
+                    sAnrReported = true;
+                    try {
+                        ActivityManager.getService().appNotResponding(
+                            "Buffer processing hung up due to stuck fence. Indicates GPU hang");
+                    } catch (RemoteException e) {
+                        // We asked the system to crash us, but the system
+                        // already crashed. Unfortunately things may be
+                        // out of control.
+                    }
+                } else {
+                    // TODO: Do something with this later. For now we just ANR
+                    // in dequeue buffer later like we always have.
+                }
+            }
+        };
+
     private String mTag = TAG;
 
     public ViewRootImpl(Context context, Display display) {
@@ -890,7 +911,6 @@
         mPreviousTransparentRegion = new Region();
         mFirst = true; // true for the first time the view is added
         mPerformContentCapture = true; // also true for the first time the view is added
-        mPerformAutoFill = true;
         mAdded = false;
         mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                 context);
@@ -2100,6 +2120,7 @@
         }
         mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl,
                 mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format);
+        mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback);
         Surface blastSurface = mBlastBufferQueue.createSurface();
         // Only call transferFrom if the surface has changed to prevent inc the generation ID and
         // causing EGL resources to be recreated.
@@ -4308,18 +4329,6 @@
         if (mPerformContentCapture) {
             performContentCaptureInitialReport();
         }
-
-        if (mPerformAutoFill) {
-            notifyEnterForAutoFillIfNeeded();
-        }
-    }
-
-    private void notifyEnterForAutoFillIfNeeded() {
-        mPerformAutoFill = false;
-        final AutofillManager afm = getAutofillManager();
-        if (afm != null) {
-            afm.notifyViewEnteredForActivityStarted(mView);
-        }
     }
 
     /**
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 0a75992..dcedb30 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -102,6 +102,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import sun.misc.Cleaner;
 
@@ -645,16 +646,6 @@
     private boolean mEnabledForAugmentedAutofillOnly;
 
     /**
-     * Indicates whether there are any fields that need to do a fill request
-     * after the activity starts.
-     *
-     * Note: This field will be set to true multiple times if there are many
-     * autofillable views. So needs to check mIsFillRequested at the same time to
-     * avoid re-trigger autofill.
-     */
-    private boolean mRequireAutofill;
-
-    /**
      * Indicates whether there is already a field to do a fill request after
      * the activity started.
      *
@@ -663,7 +654,7 @@
      * triggered autofill, it is unnecessary to trigger again through
      * AutofillManager#notifyViewEnteredForActivityStarted.
      */
-    private boolean mIsFillRequested;
+    private AtomicBoolean mIsFillRequested;
 
     @Nullable private List<AutofillId> mFillDialogTriggerIds;
 
@@ -811,8 +802,7 @@
         mContext = Objects.requireNonNull(context, "context cannot be null");
         mService = service;
         mOptions = context.getAutofillOptions();
-        mIsFillRequested = false;
-        mRequireAutofill = false;
+        mIsFillRequested = new AtomicBoolean(false);
 
         mIsFillDialogEnabled = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_AUTOFILL,
@@ -1113,22 +1103,31 @@
     }
 
     /**
-     * The view have the allowed autofill hints, marked to perform a fill request after layout if
-     * the field does not trigger a fill request.
+     * The {@link #DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED} is {@code true} or the view have
+     * the allowed autofill hints, performs a fill request to know there is any field supported
+     * fill dialog.
      *
      * @hide
      */
-    public void enableFillRequestActivityStarted(View v) {
-        if (mRequireAutofill) {
+    public void notifyViewEnteredForFillDialog(View v) {
+        // Skip if the fill request has been performed for a view.
+        if (mIsFillRequested.get()) {
             return;
         }
 
         if (mIsFillDialogEnabled
                 || ArrayUtils.containsAny(v.getAutofillHints(), mFillDialogEnabledHints)) {
             if (sDebug) {
-                Log.d(TAG, "Trigger fill request at starting");
+                Log.d(TAG, "Trigger fill request at view entered");
             }
-            mRequireAutofill = true;
+
+            // Note: No need for atomic getAndSet as this method is called on the UI thread.
+            mIsFillRequested.set(true);
+
+            int flags = FLAG_SUPPORTS_FILL_DIALOG;
+            flags |= FLAG_VIEW_NOT_FOCUSED;
+            // use root view, so autofill UI does not trigger immediately.
+            notifyViewEntered(v.getRootView(), flags);
         }
     }
 
@@ -1136,25 +1135,6 @@
         return mIsFillDialogEnabled || !ArrayUtils.isEmpty(mFillDialogEnabledHints);
     }
 
-    /**
-     * Notify autofill to do a fill request while the activity started.
-     *
-     * @hide
-     */
-    public void notifyViewEnteredForActivityStarted(@NonNull View view) {
-        if (!hasAutofillFeature() || !hasFillDialogUiFeature()) {
-            return;
-        }
-
-        if (!mRequireAutofill || mIsFillRequested) {
-            return;
-        }
-
-        int flags = FLAG_SUPPORTS_FILL_DIALOG;
-        flags |= FLAG_VIEW_NOT_FOCUSED;
-        notifyViewEntered(view, flags);
-    }
-
     private int getImeStateFlag(View v) {
         final WindowInsets rootWindowInsets = v.getRootWindowInsets();
         if (rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsets.Type.ime())) {
@@ -1203,7 +1183,7 @@
         }
         AutofillCallback callback;
         synchronized (mLock) {
-            mIsFillRequested = true;
+            mIsFillRequested.set(true);
             callback = notifyViewEnteredLocked(view, flags);
         }
 
@@ -2119,8 +2099,7 @@
         mFillableIds = null;
         mSaveTriggerId = null;
         mIdShownFillUi = null;
-        mIsFillRequested = false;
-        mRequireAutofill = false;
+        mIsFillRequested.set(false);
         mShowAutofillDialogCalled = false;
         mFillDialogTriggerIds = null;
         if (resetEnteredIds) {
diff --git a/core/java/com/android/internal/util/ImageUtils.java b/core/java/com/android/internal/util/ImageUtils.java
index 397b2c0..62dea9d 100644
--- a/core/java/com/android/internal/util/ImageUtils.java
+++ b/core/java/com/android/internal/util/ImageUtils.java
@@ -137,6 +137,18 @@
      */
     public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth,
             int maxHeight) {
+        return buildScaledBitmap(drawable, maxWidth, maxHeight, false);
+    }
+
+    /**
+     * Convert a drawable to a bitmap, scaled to fit within maxWidth and maxHeight.
+     *
+     * @param allowUpscaling if true, the drawable will not only be scaled down, but also scaled up
+     *                       to fit within the maximum size given. This is useful for converting
+     *                       vectorized icons which usually have a very small intrinsic size.
+     */
+    public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth,
+            int maxHeight, boolean allowUpscaling) {
         if (drawable == null) {
             return null;
         }
@@ -155,7 +167,9 @@
         // a large notification icon if necessary
         float ratio = Math.min((float) maxWidth / (float) originalWidth,
                 (float) maxHeight / (float) originalHeight);
-        ratio = Math.min(1.0f, ratio);
+        if (!allowUpscaling) {
+            ratio = Math.min(1.0f, ratio);
+        }
         int scaledWidth = (int) (ratio * originalWidth);
         int scaledHeight = (int) (ratio * originalHeight);
         Bitmap result = Bitmap.createBitmap(scaledWidth, scaledHeight, Config.ARGB_8888);
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index c0f7b41..4af28ea 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -47,6 +47,43 @@
     return env;
 }
 
+  struct {
+    jmethodID onTransactionHang;
+} gTransactionHangCallback;
+
+class TransactionHangCallbackWrapper : public LightRefBase<TransactionHangCallbackWrapper> {
+public:
+    explicit TransactionHangCallbackWrapper(JNIEnv* env, jobject jobject) {
+        env->GetJavaVM(&mVm);
+        mTransactionHangObject = env->NewGlobalRef(jobject);
+        LOG_ALWAYS_FATAL_IF(!mTransactionHangObject, "Failed to make global ref");
+    }
+
+    ~TransactionHangCallbackWrapper() {
+        if (mTransactionHangObject) {
+            getenv()->DeleteGlobalRef(mTransactionHangObject);
+            mTransactionHangObject = nullptr;
+        }
+    }
+
+    void onTransactionHang(bool isGpuHang) {
+        if (mTransactionHangObject) {
+            getenv()->CallVoidMethod(mTransactionHangObject,
+                                     gTransactionHangCallback.onTransactionHang, isGpuHang);
+        }
+    }
+
+private:
+    JavaVM* mVm;
+    jobject mTransactionHangObject;
+
+    JNIEnv* getenv() {
+        JNIEnv* env;
+        mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
+        return env;
+    }
+};
+
 static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName,
                           jboolean updateDestinationFrame) {
     ScopedUtfChars name(env, jName);
@@ -141,6 +178,20 @@
     sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
     return queue->isSameSurfaceControl(reinterpret_cast<SurfaceControl*>(surfaceControl));
 }
+  
+static void nativeSetTransactionHangCallback(JNIEnv* env, jclass clazz, jlong ptr,
+                                             jobject transactionHangCallback) {
+    sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
+    if (transactionHangCallback == nullptr) {
+        queue->setTransactionHangCallback(nullptr);
+    } else {
+        sp<TransactionHangCallbackWrapper> wrapper =
+                new TransactionHangCallbackWrapper{env, transactionHangCallback};
+        queue->setTransactionHangCallback([wrapper](bool isGpuHang) {
+            wrapper->onTransactionHang(isGpuHang);
+        });
+    }
+}
 
 static jobject nativeGatherPendingTransactions(JNIEnv* env, jclass clazz, jlong ptr,
                                                jlong frameNum) {
@@ -163,7 +214,10 @@
         {"nativeGetLastAcquiredFrameNum", "(J)J", (void*)nativeGetLastAcquiredFrameNum},
         {"nativeApplyPendingTransactions", "(JJ)V", (void*)nativeApplyPendingTransactions},
         {"nativeIsSameSurfaceControl", "(JJ)Z", (void*)nativeIsSameSurfaceControl},
-        {"nativeGatherPendingTransactions", "(JJ)Landroid/view/SurfaceControl$Transaction;", (void*)nativeGatherPendingTransactions}
+        {"nativeGatherPendingTransactions", "(JJ)Landroid/view/SurfaceControl$Transaction;", (void*)nativeGatherPendingTransactions},
+        {"nativeSetTransactionHangCallback",
+         "(JLandroid/graphics/BLASTBufferQueue$TransactionHangCallback;)V",
+         (void*)nativeSetTransactionHangCallback},
         // clang-format on
 };
 
@@ -180,6 +234,11 @@
     jclass consumer = FindClassOrDie(env, "java/util/function/Consumer");
     gTransactionConsumer.accept =
             GetMethodIDOrDie(env, consumer, "accept", "(Ljava/lang/Object;)V");
+    jclass transactionHangClass =
+            FindClassOrDie(env, "android/graphics/BLASTBufferQueue$TransactionHangCallback");
+    gTransactionHangCallback.onTransactionHang =
+            GetMethodIDOrDie(env, transactionHangClass, "onTransactionHang", "(Z)V");
+
     return 0;
 }
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index edaf8cf..689ff66 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5451,6 +5451,8 @@
     <bool name="config_supportsHardwareCamToggle">false</bool>
     <!-- Whether a camera intent is launched when the lens cover is toggled -->
     <bool name="config_launchCameraOnCameraLensCoverToggle">true</bool>
+    <!-- Whether changing sensor privacy SW setting requires device to be unlocked -->
+    <bool name="config_sensorPrivacyRequiresAuthentication">true</bool>
 
     <!-- List containing the allowed install sources for accessibility service. -->
     <string-array name="config_accessibility_allowed_install_source" translatable="false"/>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b882123..443f9a6 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4661,6 +4661,7 @@
   <java-symbol type="bool" name="config_supportsHardwareMicToggle" />
   <java-symbol type="bool" name="config_supportsHardwareCamToggle" />
   <java-symbol type="bool" name="config_launchCameraOnCameraLensCoverToggle" />
+  <java-symbol type="bool" name="config_sensorPrivacyRequiresAuthentication" />
 
   <java-symbol type="dimen" name="starting_surface_icon_size" />
   <java-symbol type="dimen" name="starting_surface_default_icon_size" />
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 891c82d..0e8388b 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2485,6 +2485,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "323235828": {
+      "message": "Delaying app transition for recents animation to finish",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransitionController.java"
+    },
     "327461496": {
       "message": "Complete pause: %s",
       "level": "VERBOSE",
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 4b723d1..1c41d06 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -43,6 +43,12 @@
     private static native boolean nativeIsSameSurfaceControl(long ptr, long surfaceControlPtr);
     private static native SurfaceControl.Transaction nativeGatherPendingTransactions(long ptr,
             long frameNumber);
+    private static native void nativeSetTransactionHangCallback(long ptr,
+            TransactionHangCallback callback);
+
+    public interface TransactionHangCallback {
+        void onTransactionHang(boolean isGpuHang);
+    }
 
     /** Create a new connection with the surface flinger. */
     public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height,
@@ -184,4 +190,8 @@
     public SurfaceControl.Transaction gatherPendingTransactions(long frameNumber) {
         return nativeGatherPendingTransactions(mNativeObject, frameNumber);
     }
+
+    public void setTransactionHangCallback(TransactionHangCallback hangCallback) {
+        nativeSetTransactionHangCallback(mNativeObject, hangCallback);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index 72c8141..dfd4362 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -29,6 +29,7 @@
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -63,6 +64,7 @@
             Context context,
             TvPipBoundsState tvPipBoundsState,
             TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+            PipAppOpsListener pipAppOpsListener,
             PipTaskOrganizer pipTaskOrganizer,
             TvPipMenuController tvPipMenuController,
             PipMediaController pipMediaController,
@@ -79,6 +81,7 @@
                         context,
                         tvPipBoundsState,
                         tvPipBoundsAlgorithm,
+                        pipAppOpsListener,
                         pipTaskOrganizer,
                         pipTransitionController,
                         tvPipMenuController,
@@ -140,8 +143,11 @@
     @Provides
     static TvPipNotificationController provideTvPipNotificationController(Context context,
             PipMediaController pipMediaController,
+            PipParamsChangedForwarder pipParamsChangedForwarder,
+            TvPipBoundsState tvPipBoundsState,
             @ShellMainThread Handler mainHandler) {
-        return new TvPipNotificationController(context, pipMediaController, mainHandler);
+        return new TvPipNotificationController(context, pipMediaController,
+                pipParamsChangedForwarder, tvPipBoundsState, mainHandler);
     }
 
     @WMSingleton
@@ -185,4 +191,12 @@
     static PipParamsChangedForwarder providePipParamsChangedForwarder() {
         return new PipParamsChangedForwarder();
     }
+
+    @WMSingleton
+    @Provides
+    static PipAppOpsListener providePipAppOpsListener(Context context,
+            PipTaskOrganizer pipTaskOrganizer,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new PipAppOpsListener(context, pipTaskOrganizer::removePip, mainExecutor);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 3335673..db6131a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -78,7 +78,6 @@
 import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.phone.PipAppOpsListener;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
 import com.android.wm.shell.recents.RecentTasks;
 import com.android.wm.shell.recents.RecentTasksController;
@@ -435,14 +434,6 @@
         return new FloatingContentCoordinator();
     }
 
-    @WMSingleton
-    @Provides
-    static PipAppOpsListener providePipAppOpsListener(Context context,
-            PipTouchHandler pipTouchHandler,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor);
-    }
-
     // Needs handler for registering broadcast receivers
     @WMSingleton
     @Provides
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 18a7215..1bc9e31 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -51,6 +51,7 @@
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipMediaController;
@@ -63,7 +64,6 @@
 import com.android.wm.shell.pip.PipTransitionState;
 import com.android.wm.shell.pip.PipUiEventLogger;
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
-import com.android.wm.shell.pip.phone.PipAppOpsListener;
 import com.android.wm.shell.pip.phone.PipController;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
@@ -325,6 +325,14 @@
 
     @WMSingleton
     @Provides
+    static PipAppOpsListener providePipAppOpsListener(Context context,
+            PipTouchHandler pipTouchHandler,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor);
+    }
+
+    @WMSingleton
+    @Provides
     static PipMotionHelper providePipMotionHelper(Context context,
             PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
             PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java
index d97d2d6..48a3fc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.pip.phone;
+package com.android.wm.shell.pip;
 
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
@@ -28,7 +28,6 @@
 import android.util.Pair;
 
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.pip.PipUtils;
 
 public class PipAppOpsListener {
     private static final String TAG = PipAppOpsListener.class.getSimpleName();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
index 8a50f22..65a12d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
@@ -32,6 +32,7 @@
 import android.graphics.drawable.Icon;
 import android.media.MediaMetadata;
 import android.media.session.MediaController;
+import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
 import android.media.session.PlaybackState;
 import android.os.Handler;
@@ -64,7 +65,7 @@
      */
     public interface ActionListener {
         /**
-         * Called when the media actions changes.
+         * Called when the media actions changed.
          */
         void onMediaActionsChanged(List<RemoteAction> actions);
     }
@@ -74,11 +75,21 @@
      */
     public interface MetadataListener {
         /**
-         * Called when the media metadata changes.
+         * Called when the media metadata changed.
          */
         void onMediaMetadataChanged(MediaMetadata metadata);
     }
 
+    /**
+     * A listener interface to receive notification on changes to the media session token.
+     */
+    public interface TokenListener {
+        /**
+         * Called when the media session token changed.
+         */
+        void onMediaSessionTokenChanged(MediaSession.Token token);
+    }
+
     private final Context mContext;
     private final Handler mMainHandler;
     private final HandlerExecutor mHandlerExecutor;
@@ -133,6 +144,7 @@
 
     private final ArrayList<ActionListener> mActionListeners = new ArrayList<>();
     private final ArrayList<MetadataListener> mMetadataListeners = new ArrayList<>();
+    private final ArrayList<TokenListener> mTokenListeners = new ArrayList<>();
 
     public PipMediaController(Context context, Handler mainHandler) {
         mContext = context;
@@ -204,6 +216,31 @@
         mMetadataListeners.remove(listener);
     }
 
+    /**
+     * Adds a new token listener.
+     */
+    public void addTokenListener(TokenListener listener) {
+        if (!mTokenListeners.contains(listener)) {
+            mTokenListeners.add(listener);
+            listener.onMediaSessionTokenChanged(getToken());
+        }
+    }
+
+    /**
+     * Removes a token listener.
+     */
+    public void removeTokenListener(TokenListener listener) {
+        listener.onMediaSessionTokenChanged(null);
+        mTokenListeners.remove(listener);
+    }
+
+    private MediaSession.Token getToken() {
+        if (mMediaController == null) {
+            return null;
+        }
+        return mMediaController.getSessionToken();
+    }
+
     private MediaMetadata getMediaMetadata() {
         return mMediaController != null ? mMediaController.getMetadata() : null;
     }
@@ -294,6 +331,7 @@
             }
             notifyActionsChanged();
             notifyMetadataChanged(getMediaMetadata());
+            notifyTokenChanged(getToken());
 
             // TODO(winsonc): Consider if we want to close the PIP after a timeout (like on TV)
         }
@@ -317,4 +355,10 @@
             mMetadataListeners.forEach(l -> l.onMediaMetadataChanged(metadata));
         }
     }
+
+    private void notifyTokenChanged(MediaSession.Token token) {
+        if (!mTokenListeners.isEmpty()) {
+            mTokenListeners.forEach(l -> l.onMediaSessionTokenChanged(token));
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 7df42e0..42ceb42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -76,6 +76,7 @@
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipMediaController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index e9b6bab..5a21e07 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -44,6 +44,7 @@
 import com.android.wm.shell.animation.PhysicsAnimator;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipTaskOrganizer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 8326588..7667794 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -45,6 +45,7 @@
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
@@ -97,6 +98,7 @@
 
     private final TvPipBoundsState mTvPipBoundsState;
     private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
+    private final PipAppOpsListener mAppOpsListener;
     private final PipTaskOrganizer mPipTaskOrganizer;
     private final PipMediaController mPipMediaController;
     private final TvPipNotificationController mPipNotificationController;
@@ -121,6 +123,7 @@
             Context context,
             TvPipBoundsState tvPipBoundsState,
             TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+            PipAppOpsListener pipAppOpsListener,
             PipTaskOrganizer pipTaskOrganizer,
             PipTransitionController pipTransitionController,
             TvPipMenuController tvPipMenuController,
@@ -136,6 +139,7 @@
                 context,
                 tvPipBoundsState,
                 tvPipBoundsAlgorithm,
+                pipAppOpsListener,
                 pipTaskOrganizer,
                 pipTransitionController,
                 tvPipMenuController,
@@ -153,6 +157,7 @@
             Context context,
             TvPipBoundsState tvPipBoundsState,
             TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+            PipAppOpsListener pipAppOpsListener,
             PipTaskOrganizer pipTaskOrganizer,
             PipTransitionController pipTransitionController,
             TvPipMenuController tvPipMenuController,
@@ -181,6 +186,7 @@
         mTvPipMenuController = tvPipMenuController;
         mTvPipMenuController.setDelegate(this);
 
+        mAppOpsListener = pipAppOpsListener;
         mPipTaskOrganizer = pipTaskOrganizer;
         pipTransitionController.registerPipTransitionCallback(this);
 
@@ -287,6 +293,8 @@
         }
         mTvPipBoundsState.setTvPipManuallyCollapsed(!expanding);
         mTvPipBoundsState.setTvPipExpanded(expanding);
+        mPipNotificationController.updateExpansionState();
+
         updatePinnedStackBounds();
     }
 
@@ -521,6 +529,12 @@
             @Override
             public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
                 checkIfPinnedTaskAppeared();
+                mAppOpsListener.onActivityPinned(packageName);
+            }
+
+            @Override
+            public void onActivityUnpinned() {
+                mAppOpsListener.onActivityUnpinned();
             }
 
             @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 868e456..320c05c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -494,6 +494,14 @@
         setFrameHighlighted(false);
     }
 
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        super.onWindowFocusChanged(hasWindowFocus);
+        if (!hasWindowFocus) {
+            hideAllUserControls();
+        }
+    }
+
     private void animateAlphaTo(float alpha, View view) {
         if (view.getAlpha() == alpha) {
             return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
index 4033f03..61a609d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
@@ -16,36 +16,47 @@
 
 package com.android.wm.shell.pip.tv;
 
+import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE;
+import static android.app.Notification.Action.SEMANTIC_ACTION_NONE;
+
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.RemoteAction;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
-import android.media.MediaMetadata;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.media.session.MediaSession;
+import android.os.Bundle;
 import android.os.Handler;
 import android.text.TextUtils;
 
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.ImageUtils;
 import com.android.wm.shell.R;
 import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.pip.PipParamsChangedForwarder;
+import com.android.wm.shell.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
-import java.util.Objects;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
- * A notification that informs users that PIP is running and also provides PIP controls.
- * <p>Once it's created, it will manage the PIP notification UI by itself except for handling
- * configuration changes.
+ * A notification that informs users that PiP is running and also provides PiP controls.
+ * <p>Once it's created, it will manage the PiP notification UI by itself except for handling
+ * configuration changes and user initiated expanded PiP toggling.
  */
 public class TvPipNotificationController {
     private static final String TAG = "TvPipNotification";
-    private static final boolean DEBUG = TvPipController.DEBUG;
 
     // Referenced in com.android.systemui.util.NotificationChannels.
     public static final String NOTIFICATION_CHANNEL = "TVPIP";
@@ -60,6 +71,8 @@
             "com.android.wm.shell.pip.tv.notification.action.MOVE_PIP";
     private static final String ACTION_TOGGLE_EXPANDED_PIP =
             "com.android.wm.shell.pip.tv.notification.action.TOGGLE_EXPANDED_PIP";
+    private static final String ACTION_FULLSCREEN =
+            "com.android.wm.shell.pip.tv.notification.action.FULLSCREEN";
 
     private final Context mContext;
     private final PackageManager mPackageManager;
@@ -68,44 +81,88 @@
     private final ActionBroadcastReceiver mActionBroadcastReceiver;
     private final Handler mMainHandler;
     private Delegate mDelegate;
+    private final TvPipBoundsState mTvPipBoundsState;
 
     private String mDefaultTitle;
 
+    private final List<RemoteAction> mCustomActions = new ArrayList<>();
+    private final List<RemoteAction> mMediaActions = new ArrayList<>();
+    private RemoteAction mCustomCloseAction;
+
+    private MediaSession.Token mMediaSessionToken;
+
     /** Package name for the application that owns PiP window. */
     private String mPackageName;
-    private boolean mNotified;
-    private String mMediaTitle;
-    private Bitmap mArt;
+
+    private boolean mIsNotificationShown;
+    private String mPipTitle;
+    private String mPipSubtitle;
+
+    private Bitmap mActivityIcon;
 
     public TvPipNotificationController(Context context, PipMediaController pipMediaController,
+            PipParamsChangedForwarder pipParamsChangedForwarder, TvPipBoundsState tvPipBoundsState,
             Handler mainHandler) {
         mContext = context;
         mPackageManager = context.getPackageManager();
         mNotificationManager = context.getSystemService(NotificationManager.class);
         mMainHandler = mainHandler;
+        mTvPipBoundsState = tvPipBoundsState;
 
         mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL)
                 .setLocalOnly(true)
-                .setOngoing(false)
+                .setOngoing(true)
                 .setCategory(Notification.CATEGORY_SYSTEM)
                 .setShowWhen(true)
                 .setSmallIcon(R.drawable.pip_icon)
+                .setAllowSystemGeneratedContextualActions(false)
+                .setContentIntent(createPendingIntent(context, ACTION_FULLSCREEN))
+                .setDeleteIntent(getCloseAction().actionIntent)
                 .extend(new Notification.TvExtender()
                         .setContentIntent(createPendingIntent(context, ACTION_SHOW_PIP_MENU))
                         .setDeleteIntent(createPendingIntent(context, ACTION_CLOSE_PIP)));
 
         mActionBroadcastReceiver = new ActionBroadcastReceiver();
 
-        pipMediaController.addMetadataListener(this::onMediaMetadataChanged);
+        pipMediaController.addActionListener(this::onMediaActionsChanged);
+        pipMediaController.addTokenListener(this::onMediaSessionTokenChanged);
+
+        pipParamsChangedForwarder.addListener(
+                new PipParamsChangedForwarder.PipParamsChangedCallback() {
+                    @Override
+                    public void onExpandedAspectRatioChanged(float ratio) {
+                        updateExpansionState();
+                    }
+
+                    @Override
+                    public void onActionsChanged(List<RemoteAction> actions,
+                            RemoteAction closeAction) {
+                        mCustomActions.clear();
+                        mCustomActions.addAll(actions);
+                        mCustomCloseAction = closeAction;
+                        updateNotificationContent();
+                    }
+
+                    @Override
+                    public void onTitleChanged(String title) {
+                        mPipTitle = title;
+                        updateNotificationContent();
+                    }
+
+                    @Override
+                    public void onSubtitleChanged(String subtitle) {
+                        mPipSubtitle = subtitle;
+                        updateNotificationContent();
+                    }
+                });
 
         onConfigurationChanged(context);
     }
 
     void setDelegate(Delegate delegate) {
-        if (DEBUG) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: setDelegate(), delegate=%s", TAG, delegate);
-        }
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: setDelegate(), delegate=%s",
+                TAG, delegate);
+
         if (mDelegate != null) {
             throw new IllegalStateException(
                     "The delegate has already been set and should not change.");
@@ -118,90 +175,181 @@
     }
 
     void show(String packageName) {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: show %s", TAG, packageName);
         if (mDelegate == null) {
             throw new IllegalStateException("Delegate is not set.");
         }
 
+        mIsNotificationShown = true;
         mPackageName = packageName;
-        update();
+        mActivityIcon = getActivityIcon();
         mActionBroadcastReceiver.register();
+
+        updateNotificationContent();
     }
 
     void dismiss() {
-        mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
-        mNotified = false;
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: dismiss()", TAG);
+
+        mIsNotificationShown = false;
         mPackageName = null;
         mActionBroadcastReceiver.unregister();
+
+        mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
     }
 
-    private void onMediaMetadataChanged(MediaMetadata metadata) {
-        if (updateMediaControllerMetadata(metadata) && mNotified) {
-            // update notification
-            update();
+    private Notification.Action getToggleAction(boolean expanded) {
+        if (expanded) {
+            return createSystemAction(R.drawable.pip_ic_collapse,
+                    R.string.pip_collapse, ACTION_TOGGLE_EXPANDED_PIP);
+        } else {
+            return createSystemAction(R.drawable.pip_ic_expand, R.string.pip_expand,
+                    ACTION_TOGGLE_EXPANDED_PIP);
         }
     }
 
+    private Notification.Action createSystemAction(int iconRes, int titleRes, String action) {
+        Notification.Action.Builder builder = new Notification.Action.Builder(
+                Icon.createWithResource(mContext, iconRes),
+                mContext.getString(titleRes),
+                createPendingIntent(mContext, action));
+        builder.setContextual(true);
+        return builder.build();
+    }
+
+    private void onMediaActionsChanged(List<RemoteAction> actions) {
+        mMediaActions.clear();
+        mMediaActions.addAll(actions);
+        if (mCustomActions.isEmpty()) {
+            updateNotificationContent();
+        }
+    }
+
+    private void onMediaSessionTokenChanged(MediaSession.Token token) {
+        mMediaSessionToken = token;
+        updateNotificationContent();
+    }
+
+    private Notification.Action remoteToNotificationAction(RemoteAction action) {
+        return remoteToNotificationAction(action, SEMANTIC_ACTION_NONE);
+    }
+
+    private Notification.Action remoteToNotificationAction(RemoteAction action,
+            int semanticAction) {
+        Notification.Action.Builder builder = new Notification.Action.Builder(action.getIcon(),
+                action.getTitle(),
+                action.getActionIntent());
+        if (action.getContentDescription() != null) {
+            Bundle extras = new Bundle();
+            extras.putCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION,
+                    action.getContentDescription());
+            builder.addExtras(extras);
+        }
+        builder.setSemanticAction(semanticAction);
+        builder.setContextual(true);
+        return builder.build();
+    }
+
+    private Notification.Action[] getNotificationActions() {
+        final List<Notification.Action> actions = new ArrayList<>();
+
+        // 1. Fullscreen
+        actions.add(getFullscreenAction());
+        // 2. Close
+        actions.add(getCloseAction());
+        // 3. App actions
+        final List<RemoteAction> appActions =
+                mCustomActions.isEmpty() ? mMediaActions : mCustomActions;
+        for (RemoteAction appAction : appActions) {
+            if (PipUtils.remoteActionsMatch(mCustomCloseAction, appAction)
+                    || !appAction.isEnabled()) {
+                continue;
+            }
+            actions.add(remoteToNotificationAction(appAction));
+        }
+        // 4. Move
+        actions.add(getMoveAction());
+        // 5. Toggle expansion (if expanded PiP enabled)
+        if (mTvPipBoundsState.getDesiredTvExpandedAspectRatio() > 0
+                && mTvPipBoundsState.isTvExpandedPipSupported()) {
+            actions.add(getToggleAction(mTvPipBoundsState.isTvPipExpanded()));
+        }
+        return actions.toArray(new Notification.Action[0]);
+    }
+
+    private Notification.Action getCloseAction() {
+        if (mCustomCloseAction == null) {
+            return createSystemAction(R.drawable.pip_ic_close_white, R.string.pip_close,
+                    ACTION_CLOSE_PIP);
+        } else {
+            return remoteToNotificationAction(mCustomCloseAction, SEMANTIC_ACTION_DELETE);
+        }
+    }
+
+    private Notification.Action getFullscreenAction() {
+        return createSystemAction(R.drawable.pip_ic_fullscreen_white,
+                R.string.pip_fullscreen, ACTION_FULLSCREEN);
+    }
+
+    private Notification.Action getMoveAction() {
+        return createSystemAction(R.drawable.pip_ic_move_white, R.string.pip_move,
+                ACTION_MOVE_PIP);
+    }
+
     /**
-     * Called by {@link PipController} when the configuration is changed.
+     * Called by {@link TvPipController} when the configuration is changed.
      */
     void onConfigurationChanged(Context context) {
         mDefaultTitle = context.getResources().getString(R.string.pip_notification_unknown_title);
-        if (mNotified) {
-            // Update the notification.
-            update();
-        }
+        updateNotificationContent();
     }
 
-    private void update() {
-        mNotified = true;
+    void updateExpansionState() {
+        updateNotificationContent();
+    }
+
+    private void updateNotificationContent() {
+        if (mPackageManager == null || !mIsNotificationShown) {
+            return;
+        }
+
+        Notification.Action[] actions = getNotificationActions();
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: update(), title: %s, subtitle: %s, mediaSessionToken: %s, #actions: %s", TAG,
+                getNotificationTitle(), mPipSubtitle, mMediaSessionToken, actions.length);
+        for (Notification.Action action : actions) {
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: action: %s", TAG,
+                    action.toString());
+        }
+
         mNotificationBuilder
                 .setWhen(System.currentTimeMillis())
-                .setContentTitle(getNotificationTitle());
-        if (mArt != null) {
-            mNotificationBuilder.setStyle(new Notification.BigPictureStyle()
-                    .bigPicture(mArt));
-        } else {
-            mNotificationBuilder.setStyle(null);
-        }
+                .setContentTitle(getNotificationTitle())
+                .setContentText(mPipSubtitle)
+                .setSubText(getApplicationLabel(mPackageName))
+                .setActions(actions);
+        setPipIcon();
+
+        Bundle extras = new Bundle();
+        extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, mMediaSessionToken);
+        mNotificationBuilder.setExtras(extras);
+
+        // TvExtender not recognized if not set last.
+        mNotificationBuilder.extend(new Notification.TvExtender()
+                .setContentIntent(createPendingIntent(mContext, ACTION_SHOW_PIP_MENU))
+                .setDeleteIntent(createPendingIntent(mContext, ACTION_CLOSE_PIP)));
         mNotificationManager.notify(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP,
                 mNotificationBuilder.build());
     }
 
-    private boolean updateMediaControllerMetadata(MediaMetadata metadata) {
-        String title = null;
-        Bitmap art = null;
-        if (metadata != null) {
-            title = metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE);
-            if (TextUtils.isEmpty(title)) {
-                title = metadata.getString(MediaMetadata.METADATA_KEY_TITLE);
-            }
-            art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
-            if (art == null) {
-                art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
-            }
-        }
-
-        if (TextUtils.equals(title, mMediaTitle) && Objects.equals(art, mArt)) {
-            return false;
-        }
-
-        mMediaTitle = title;
-        mArt = art;
-
-        return true;
-    }
-
-
     private String getNotificationTitle() {
-        if (!TextUtils.isEmpty(mMediaTitle)) {
-            return mMediaTitle;
+        if (!TextUtils.isEmpty(mPipTitle)) {
+            return mPipTitle;
         }
-
         final String applicationTitle = getApplicationLabel(mPackageName);
         if (!TextUtils.isEmpty(applicationTitle)) {
             return applicationTitle;
         }
-
         return mDefaultTitle;
     }
 
@@ -214,10 +362,37 @@
         }
     }
 
+    private void setPipIcon() {
+        if (mActivityIcon != null) {
+            mNotificationBuilder.setLargeIcon(mActivityIcon);
+            return;
+        }
+        // Fallback: Picture-in-Picture icon
+        mNotificationBuilder.setLargeIcon(Icon.createWithResource(mContext, R.drawable.pip_icon));
+    }
+
+    private Bitmap getActivityIcon() {
+        if (mContext == null) return null;
+        ComponentName componentName = PipUtils.getTopPipActivity(mContext).first;
+        if (componentName == null) return null;
+
+        Drawable drawable;
+        try {
+            drawable = mPackageManager.getActivityIcon(componentName);
+        } catch (PackageManager.NameNotFoundException e) {
+            return null;
+        }
+        int width = mContext.getResources().getDimensionPixelSize(
+                android.R.dimen.notification_large_icon_width);
+        int height = mContext.getResources().getDimensionPixelSize(
+                android.R.dimen.notification_large_icon_height);
+        return ImageUtils.buildScaledBitmap(drawable, width, height, /* allowUpscaling */ true);
+    }
+
     private static PendingIntent createPendingIntent(Context context, String action) {
         return PendingIntent.getBroadcast(context, 0,
                 new Intent(action).setPackage(context.getPackageName()),
-                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
     }
 
     private class ActionBroadcastReceiver extends BroadcastReceiver {
@@ -228,6 +403,7 @@
             mIntentFilter.addAction(ACTION_SHOW_PIP_MENU);
             mIntentFilter.addAction(ACTION_MOVE_PIP);
             mIntentFilter.addAction(ACTION_TOGGLE_EXPANDED_PIP);
+            mIntentFilter.addAction(ACTION_FULLSCREEN);
         }
         boolean mRegistered = false;
 
@@ -249,10 +425,8 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
-            if (DEBUG) {
-                ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                        "%s: on(Broadcast)Receive(), action=%s", TAG, action);
-            }
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: on(Broadcast)Receive(), action=%s", TAG, action);
 
             if (ACTION_SHOW_PIP_MENU.equals(action)) {
                 mDelegate.showPictureInPictureMenu();
@@ -262,14 +436,21 @@
                 mDelegate.enterPipMovementMenu();
             } else if (ACTION_TOGGLE_EXPANDED_PIP.equals(action)) {
                 mDelegate.togglePipExpansion();
+            } else if (ACTION_FULLSCREEN.equals(action)) {
+                mDelegate.movePipToFullscreen();
             }
         }
     }
 
     interface Delegate {
         void showPictureInPictureMenu();
+
         void closePip();
+
         void enterPipMovementMenu();
+
         void togglePipExpansion();
+
+        void movePipToFullscreen();
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index bf08261..df18133 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -45,6 +45,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipMediaController;
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index e7432ac..90c4440 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -136,24 +136,59 @@
         free(valueBuffer);
         return nullptr;
     }
+    mNumShadersCachedInRam++;
+    ATRACE_FORMAT("HWUI RAM cache: %d shaders", mNumShadersCachedInRam);
     return SkData::MakeFromMalloc(valueBuffer, valueSize);
 }
 
+namespace {
+// Helper for BlobCache::set to trace the result.
+void set(BlobCache* cache, const void* key, size_t keySize, const void* value, size_t valueSize) {
+    switch (cache->set(key, keySize, value, valueSize)) {
+        case BlobCache::InsertResult::kInserted:
+            // This is what we expect/hope. It means the cache is large enough.
+            return;
+        case BlobCache::InsertResult::kDidClean: {
+            ATRACE_FORMAT("ShaderCache: evicted an entry to fit {key: %lu value %lu}!", keySize,
+                          valueSize);
+            return;
+        }
+        case BlobCache::InsertResult::kNotEnoughSpace: {
+            ATRACE_FORMAT("ShaderCache: could not fit {key: %lu value %lu}!", keySize, valueSize);
+            return;
+        }
+        case BlobCache::InsertResult::kInvalidValueSize:
+        case BlobCache::InsertResult::kInvalidKeySize: {
+            ATRACE_FORMAT("ShaderCache: invalid size {key: %lu value %lu}!", keySize, valueSize);
+            return;
+        }
+        case BlobCache::InsertResult::kKeyTooBig:
+        case BlobCache::InsertResult::kValueTooBig:
+        case BlobCache::InsertResult::kCombinedTooBig: {
+            ATRACE_FORMAT("ShaderCache: entry too big: {key: %lu value %lu}!", keySize, valueSize);
+            return;
+        }
+    }
+}
+}  // namespace
+
 void ShaderCache::saveToDiskLocked() {
     ATRACE_NAME("ShaderCache::saveToDiskLocked");
     if (mInitialized && mBlobCache && mSavePending) {
         if (mIDHash.size()) {
             auto key = sIDKey;
-            mBlobCache->set(&key, sizeof(key), mIDHash.data(), mIDHash.size());
+            set(mBlobCache.get(), &key, sizeof(key), mIDHash.data(), mIDHash.size());
         }
         mBlobCache->writeToFile();
     }
     mSavePending = false;
 }
 
-void ShaderCache::store(const SkData& key, const SkData& data) {
+void ShaderCache::store(const SkData& key, const SkData& data, const SkString& /*description*/) {
     ATRACE_NAME("ShaderCache::store");
     std::lock_guard<std::mutex> lock(mMutex);
+    mNumShadersCachedInRam++;
+    ATRACE_FORMAT("HWUI RAM cache: %d shaders", mNumShadersCachedInRam);
 
     if (!mInitialized) {
         return;
@@ -187,7 +222,7 @@
         mNewPipelineCacheSize = -1;
         mTryToStorePipelineCache = true;
     }
-    bc->set(key.data(), keySize, value, valueSize);
+    set(bc, key.data(), keySize, value, valueSize);
 
     if (!mSavePending && mDeferredSaveDelay > 0) {
         mSavePending = true;
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index 4dcc9fb..3e0fd51 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -73,7 +73,7 @@
      * "store" attempts to insert a new key/value blob pair into the cache.
      * This will be called by Skia after it compiled a new SKSL shader
      */
-    void store(const SkData& key, const SkData& data) override;
+    void store(const SkData& key, const SkData& data, const SkString& description) override;
 
     /**
      * "onVkFrameFlushed" tries to store Vulkan pipeline cache state.
@@ -210,6 +210,13 @@
      */
     static constexpr uint8_t sIDKey = 0;
 
+    /**
+     * Most of this class concerns persistent storage for shaders, but it's also
+     * interesting to keep track of how many shaders are stored in RAM. This
+     * class provides a convenient entry point for that.
+     */
+    int mNumShadersCachedInRam = 0;
+
     friend class ShaderCacheTestUtils;  // used for unit testing
 };
 
diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp
index 87981f1..974d85a 100644
--- a/libs/hwui/tests/unit/ShaderCacheTests.cpp
+++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp
@@ -140,9 +140,9 @@
     // write to the in-memory cache without storing on disk and verify we read the same values
     sk_sp<SkData> inVS;
     setShader(inVS, "sassas");
-    ShaderCache::get().store(GrProgramDescTest(100), *inVS.get());
+    ShaderCache::get().store(GrProgramDescTest(100), *inVS.get(), SkString());
     setShader(inVS, "someVS");
-    ShaderCache::get().store(GrProgramDescTest(432), *inVS.get());
+    ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
     ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(100))), sk_sp<SkData>());
     ASSERT_TRUE(checkShader(outVS, "sassas"));
     ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
@@ -166,7 +166,7 @@
 
     // change data, store to disk, read back again and verify data has been changed
     setShader(inVS, "ewData1");
-    ShaderCache::get().store(GrProgramDescTest(432), *inVS.get());
+    ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
     ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
     ShaderCache::get().initShaderDiskCache();
     ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
@@ -177,7 +177,7 @@
     std::vector<uint8_t> dataBuffer(dataSize);
     genRandomData(dataBuffer);
     setShader(inVS, dataBuffer);
-    ShaderCache::get().store(GrProgramDescTest(432), *inVS.get());
+    ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
     ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
     ShaderCache::get().initShaderDiskCache();
     ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
@@ -225,7 +225,7 @@
         setShader(data, dataBuffer);
 
         blob = std::make_pair(key, data);
-        ShaderCache::get().store(*key.get(), *data.get());
+        ShaderCache::get().store(*key.get(), *data.get(), SkString());
     }
     ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
 
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 1dc74e5..10ea651 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -106,6 +106,7 @@
 PointerController::~PointerController() {
     mDisplayInfoListener->onPointerControllerDestroyed();
     mUnregisterWindowInfosListener(mDisplayInfoListener);
+    mContext.getPolicy()->onPointerDisplayIdChanged(ADISPLAY_ID_NONE, 0, 0);
 }
 
 std::mutex& PointerController::getLock() const {
@@ -255,6 +256,12 @@
         getAdditionalMouseResources = true;
     }
     mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
+    if (viewport.displayId != mLocked.pointerDisplayId) {
+        float xPos, yPos;
+        mCursorController.getPosition(&xPos, &yPos);
+        mContext.getPolicy()->onPointerDisplayIdChanged(viewport.displayId, xPos, yPos);
+        mLocked.pointerDisplayId = viewport.displayId;
+    }
 }
 
 void PointerController::updatePointerIcon(int32_t iconId) {
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 2e6e851..eab030f 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -104,6 +104,7 @@
 
     struct Locked {
         Presentation presentation;
+        int32_t pointerDisplayId = ADISPLAY_ID_NONE;
 
         std::vector<gui::DisplayInfo> mDisplayInfos;
         std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers;
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
index 26a65a4..c2bc1e0 100644
--- a/libs/input/PointerControllerContext.h
+++ b/libs/input/PointerControllerContext.h
@@ -79,6 +79,7 @@
             std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) = 0;
     virtual int32_t getDefaultPointerIconId() = 0;
     virtual int32_t getCustomPointerIconId() = 0;
+    virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) = 0;
 };
 
 /*
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index dae1fcc..f9752ed 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -56,9 +56,11 @@
             std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) override;
     virtual int32_t getDefaultPointerIconId() override;
     virtual int32_t getCustomPointerIconId() override;
+    virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) override;
 
     bool allResourcesAreLoaded();
     bool noResourcesAreLoaded();
+    std::optional<int32_t> getLastReportedPointerDisplayId() { return latestPointerDisplayId; }
 
 private:
     void loadPointerIconForType(SpriteIcon* icon, int32_t cursorType);
@@ -66,6 +68,7 @@
     bool pointerIconLoaded{false};
     bool pointerResourcesLoaded{false};
     bool additionalMouseResourcesLoaded{false};
+    std::optional<int32_t /*displayId*/> latestPointerDisplayId;
 };
 
 void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, int32_t) {
@@ -126,12 +129,19 @@
     icon->hotSpotX = hotSpot.first;
     icon->hotSpotY = hotSpot.second;
 }
+
+void MockPointerControllerPolicyInterface::onPointerDisplayIdChanged(int32_t displayId,
+                                                                     float /*xPos*/,
+                                                                     float /*yPos*/) {
+    latestPointerDisplayId = displayId;
+}
+
 class PointerControllerTest : public Test {
 protected:
     PointerControllerTest();
     ~PointerControllerTest();
 
-    void ensureDisplayViewportIsSet();
+    void ensureDisplayViewportIsSet(int32_t displayId = ADISPLAY_ID_DEFAULT);
 
     sp<MockSprite> mPointerSprite;
     sp<MockPointerControllerPolicyInterface> mPolicy;
@@ -168,9 +178,9 @@
     mThread.join();
 }
 
-void PointerControllerTest::ensureDisplayViewportIsSet() {
+void PointerControllerTest::ensureDisplayViewportIsSet(int32_t displayId) {
     DisplayViewport viewport;
-    viewport.displayId = ADISPLAY_ID_DEFAULT;
+    viewport.displayId = displayId;
     viewport.logicalRight = 1600;
     viewport.logicalBottom = 1200;
     viewport.physicalRight = 800;
@@ -255,6 +265,30 @@
     ensureDisplayViewportIsSet();
 }
 
+TEST_F(PointerControllerTest, notifiesPolicyWhenPointerDisplayChanges) {
+    EXPECT_FALSE(mPolicy->getLastReportedPointerDisplayId())
+            << "A pointer display change does not occur when PointerController is created.";
+
+    ensureDisplayViewportIsSet(ADISPLAY_ID_DEFAULT);
+
+    const auto lastReportedPointerDisplayId = mPolicy->getLastReportedPointerDisplayId();
+    ASSERT_TRUE(lastReportedPointerDisplayId)
+            << "The policy is notified of a pointer display change when the viewport is first set.";
+    EXPECT_EQ(ADISPLAY_ID_DEFAULT, *lastReportedPointerDisplayId)
+            << "Incorrect pointer display notified.";
+
+    ensureDisplayViewportIsSet(42);
+
+    EXPECT_EQ(42, *mPolicy->getLastReportedPointerDisplayId())
+            << "The policy is notified when the pointer display changes.";
+
+    // Release the PointerController.
+    mPointerController = nullptr;
+
+    EXPECT_EQ(ADISPLAY_ID_NONE, *mPolicy->getLastReportedPointerDisplayId())
+            << "The pointer display changes to invalid when PointerController is destroyed.";
+}
+
 class PointerControllerWindowInfoListenerTest : public Test {};
 
 class TestPointerController : public PointerController {
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index 70d6810..472586b 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -643,6 +643,9 @@
 
     /**
      * <p>Return the frame to the ImageReader for reuse.</p>
+     *
+     * This method should only be called via {@link SurfaceImage#close} which ensures that image
+     * closing is atomic.
      */
     private void releaseImage(Image i) {
         if (! (i instanceof SurfaceImage) ) {
@@ -1125,6 +1128,8 @@
     }
 
     private class SurfaceImage extends android.media.Image {
+        private final Object mCloseLock = new Object();
+
         public SurfaceImage(int format) {
             mFormat = format;
             mHardwareBufferFormat = ImageReader.this.mHardwareBufferFormat;
@@ -1139,7 +1144,9 @@
 
         @Override
         public void close() {
-            ImageReader.this.releaseImage(this);
+            synchronized (this.mCloseLock) {
+                ImageReader.this.releaseImage(this);
+            }
         }
 
         public ImageReader getReader() {
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index b230438..10bb6cb 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -121,6 +121,30 @@
             android:adjustViewBounds="true"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"/>
+        <TextView
+            android:id="@+id/hidden_text_preview"
+            android:visibility="gone"
+            android:textFontWeight="500"
+            android:padding="8dp"
+            android:gravity="center"
+            android:textSize="14sp"
+            android:text="@string/clipboard_text_hidden"
+            android:textColor="?attr/overlayButtonTextColor"
+            android:background="?androidprv:attr/colorAccentSecondary"
+            android:layout_width="@dimen/clipboard_preview_size"
+            android:layout_height="@dimen/clipboard_preview_size"/>
+        <TextView
+            android:id="@+id/hidden_image_preview"
+            android:visibility="gone"
+            android:textFontWeight="500"
+            android:padding="8dp"
+            android:gravity="center"
+            android:textSize="14sp"
+            android:text="@string/clipboard_text_hidden"
+            android:textColor="#ffffff"
+            android:background="#000000"
+            android:layout_width="@dimen/clipboard_preview_size"
+            android:layout_height="@dimen/clipboard_preview_size"/>
     </FrameLayout>
     <FrameLayout
         android:id="@+id/dismiss_button"
@@ -141,4 +165,4 @@
             android:layout_margin="@dimen/overlay_dismiss_button_margin"
             android:src="@drawable/overlay_cancel"/>
     </FrameLayout>
-</com.android.systemui.screenshot.DraggableConstraintLayout>
+</com.android.systemui.screenshot.DraggableConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/fgs_manager_app_item.xml b/packages/SystemUI/res/layout/fgs_manager_app_item.xml
index 30bce9d..a6f659d 100644
--- a/packages/SystemUI/res/layout/fgs_manager_app_item.xml
+++ b/packages/SystemUI/res/layout/fgs_manager_app_item.xml
@@ -50,7 +50,8 @@
   <Button
       android:id="@+id/fgs_manager_app_item_stop_button"
       android:layout_width="wrap_content"
-      android:layout_height="48dp"
+      android:layout_height="wrap_content"
+      android:minHeight="48dp"
       android:text="@string/fgs_manager_app_item_stop_button_label"
       android:layout_marginStart="12dp"
       style="?android:attr/buttonBarNeutralButtonStyle" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 89b72dd..2426f01 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1211,7 +1211,7 @@
     <string name="wallet_app_button_label">Show all</string>
     <!-- Label of the button underneath the card carousel prompting user unlock device. [CHAR LIMIT=NONE] -->
     <!-- Secondary label of the quick access wallet tile if no card. [CHAR LIMIT=NONE] -->
-    <string name="wallet_secondary_label_no_card">Add a card</string>
+    <string name="wallet_secondary_label_no_card">Tap to open</string>
     <!-- Secondary label of the quick access wallet tile if wallet is still updating. [CHAR LIMIT=NONE] -->
     <string name="wallet_secondary_label_updating">Updating</string>
     <!-- Secondary label of the quick access wallet tile if device locked. [CHAR LIMIT=NONE] -->
@@ -2482,6 +2482,8 @@
     <string name="clipboard_edit_image_description">Edit copied image</string>
     <!-- Label for button to send copied content to a nearby device [CHAR LIMIT=NONE] -->
     <string name="clipboard_send_nearby_description">Send to nearby device</string>
+    <!-- Text informing user that copied content is hidden [CHAR LIMIT=NONE] -->
+    <string name="clipboard_text_hidden">Tap to view</string>
 
     <!-- Generic "add" string [CHAR LIMIT=NONE] -->
     <string name="add">Add</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index c93c065..f9e73ec 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1111,8 +1111,9 @@
     </style>
 
     <style name="FgsManagerAppDuration">
-        <item name="android:fontFamily">?android:attr/textAppearanceSmall</item>
         <item name="android:textDirection">locale</item>
+        <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
+        <item name="android:textColor">?android:attr/textColorSecondary</item>
     </style>
 
     <style name="BroadcastDialog">
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 41f9240..39c3949 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -282,7 +282,7 @@
         super.reloadColors();
         mMessageAreaController.reloadColors();
         int textColor = Utils.getColorAttr(mLockPatternView.getContext(),
-                android.R.attr.textColorPrimary).getDefaultColor();
+                android.R.attr.textColorSecondary).getDefaultColor();
         int errorColor = Utils.getColorError(mLockPatternView.getContext()).getDefaultColor();
         mLockPatternView.setColors(textColor, textColor, errorColor);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
index 2f09792..8de7213 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
@@ -154,7 +154,9 @@
 
         //re-calculate the height of description
         View description = mView.findViewById(R.id.description);
-        totalHeight += measureDescription(description, displayHeight, width, totalHeight);
+        if (description != null && description.getVisibility() != View.GONE) {
+            totalHeight += measureDescription(description, displayHeight, width, totalHeight);
+        }
 
         return new AuthDialog.LayoutParams(width, totalHeight);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 726d00c..ee8363f 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -38,6 +38,7 @@
 import android.app.RemoteAction;
 import android.content.BroadcastReceiver;
 import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -128,6 +129,8 @@
     private final View mClipboardPreview;
     private final ImageView mImagePreview;
     private final TextView mTextPreview;
+    private final TextView mHiddenTextPreview;
+    private final TextView mHiddenImagePreview;
     private final View mPreviewBorder;
     private final OverlayActionChip mEditChip;
     private final OverlayActionChip mRemoteCopyChip;
@@ -186,6 +189,8 @@
         mClipboardPreview = requireNonNull(mView.findViewById(R.id.clipboard_preview));
         mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview));
         mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
+        mHiddenTextPreview = requireNonNull(mView.findViewById(R.id.hidden_text_preview));
+        mHiddenImagePreview = requireNonNull(mView.findViewById(R.id.hidden_image_preview));
         mPreviewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
         mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
         mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
@@ -270,21 +275,32 @@
             mExitAnimator.cancel();
         }
         reset();
+
+        boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null
+                && clipData.getDescription().getExtras()
+                .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
         if (clipData == null || clipData.getItemCount() == 0) {
-            showTextPreview(mContext.getResources().getString(
-                    R.string.clipboard_overlay_text_copied));
+            showTextPreview(
+                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+                    mTextPreview);
         } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
             ClipData.Item item = clipData.getItemAt(0);
             if (item.getTextLinks() != null) {
                 AsyncTask.execute(() -> classifyText(clipData.getItemAt(0), clipSource));
             }
-            showEditableText(item.getText());
+            if (isSensitive) {
+                showEditableText(
+                        mContext.getResources().getString(R.string.clipboard_text_hidden), true);
+            } else {
+                showEditableText(item.getText(), false);
+            }
         } else if (clipData.getItemAt(0).getUri() != null) {
             // How to handle non-image URIs?
-            showEditableImage(clipData.getItemAt(0).getUri());
+            showEditableImage(clipData.getItemAt(0).getUri(), isSensitive);
         } else {
             showTextPreview(
-                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied));
+                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+                    mTextPreview);
         }
         Intent remoteCopyIntent = getRemoteCopyIntent(clipData);
         // Only show remote copy if it's available.
@@ -406,15 +422,23 @@
         animateOut();
     }
 
-    private void showTextPreview(CharSequence text) {
-        mTextPreview.setVisibility(View.VISIBLE);
+    private void showSinglePreview(View v) {
+        mTextPreview.setVisibility(View.GONE);
         mImagePreview.setVisibility(View.GONE);
-        mTextPreview.setText(text.subSequence(0, Math.min(500, text.length())));
+        mHiddenTextPreview.setVisibility(View.GONE);
+        mHiddenImagePreview.setVisibility(View.GONE);
+        v.setVisibility(View.VISIBLE);
+    }
+
+    private void showTextPreview(CharSequence text, TextView textView) {
+        showSinglePreview(textView);
+        textView.setText(text.subSequence(0, Math.min(500, text.length())));
         mEditChip.setVisibility(View.GONE);
     }
 
-    private void showEditableText(CharSequence text) {
-        showTextPreview(text);
+    private void showEditableText(CharSequence text, boolean hidden) {
+        TextView textView = hidden ? mHiddenTextPreview : mTextPreview;
+        showTextPreview(text, textView);
         mEditChip.setVisibility(View.VISIBLE);
         mActionContainerBackground.setVisibility(View.VISIBLE);
         mEditChip.setAlpha(1f);
@@ -422,32 +446,36 @@
                 mContext.getString(R.string.clipboard_edit_text_description));
         View.OnClickListener listener = v -> editText();
         mEditChip.setOnClickListener(listener);
-        mTextPreview.setOnClickListener(listener);
+        textView.setOnClickListener(listener);
     }
 
-    private void showEditableImage(Uri uri) {
-        ContentResolver resolver = mContext.getContentResolver();
-        try {
-            int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
-            // The width of the view is capped, height maintains aspect ratio, so allow it to be
-            // taller if needed.
-            Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
-            mImagePreview.setImageBitmap(thumbnail);
-        } catch (IOException e) {
-            Log.e(TAG, "Thumbnail loading failed", e);
-            showTextPreview(
-                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied));
-            return;
-        }
-        mTextPreview.setVisibility(View.GONE);
-        mImagePreview.setVisibility(View.VISIBLE);
+    private void showEditableImage(Uri uri, boolean isSensitive) {
         mEditChip.setAlpha(1f);
         mActionContainerBackground.setVisibility(View.VISIBLE);
         View.OnClickListener listener = v -> editImage(uri);
+        if (isSensitive) {
+            showSinglePreview(mHiddenImagePreview);
+            mHiddenImagePreview.setOnClickListener(listener);
+        } else {
+            showSinglePreview(mImagePreview);
+            ContentResolver resolver = mContext.getContentResolver();
+            try {
+                int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
+                // The width of the view is capped, height maintains aspect ratio, so allow it to be
+                // taller if needed.
+                Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
+                mImagePreview.setImageBitmap(thumbnail);
+            } catch (IOException e) {
+                Log.e(TAG, "Thumbnail loading failed", e);
+                showTextPreview(
+                        mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+                        mTextPreview);
+            }
+            mImagePreview.setOnClickListener(listener);
+        }
         mEditChip.setOnClickListener(listener);
         mEditChip.setContentDescription(
                 mContext.getString(R.string.clipboard_edit_image_description));
-        mImagePreview.setOnClickListener(listener);
     }
 
     private Intent getRemoteCopyIntent(ClipData clipData) {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
index 0d89879..b54b832 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
@@ -20,10 +20,12 @@
 
 import android.app.Activity;
 import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.ClipboardManager;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
+import android.os.PersistableBundle;
 import android.util.Log;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
@@ -41,6 +43,7 @@
     private EditText mEditText;
     private ClipboardManager mClipboardManager;
     private TextView mAttribution;
+    private boolean mSensitive;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -72,6 +75,9 @@
         }
         mEditText.setText(clip.getItemAt(0).getText());
         mEditText.requestFocus();
+        mSensitive = clip.getDescription().getExtras() != null
+                && clip.getDescription().getExtras()
+                .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
         mClipboardManager.addPrimaryClipChangedListener(this);
     }
 
@@ -88,6 +94,9 @@
 
     private void saveToClipboard() {
         ClipData clip = ClipData.newPlainText("text", mEditText.getText());
+        PersistableBundle extras = new PersistableBundle();
+        extras.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, mSensitive);
+        clip.getDescription().setExtras(extras);
         mClipboardManager.setPrimaryClip(clip);
         hideImeAndFinish();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 9aebb9d..14a7e3c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -44,6 +44,7 @@
 import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
 import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController
 import com.android.systemui.shared.system.smartspace.SmartspaceState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import dagger.Lazy
@@ -140,7 +141,8 @@
     keyguardViewMediator: Lazy<KeyguardViewMediator>,
     private val keyguardViewController: KeyguardViewController,
     private val featureFlags: FeatureFlags,
-    private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>
+    private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
+    private val statusBarStateController: SysuiStatusBarStateController
 ) : KeyguardStateController.Callback, ISysuiUnlockAnimationController.Stub() {
 
     interface KeyguardUnlockAnimationListener {
@@ -372,7 +374,8 @@
      * changed.
      */
     override fun onKeyguardGoingAwayChanged() {
-        if (keyguardStateController.isKeyguardGoingAway) {
+        if (keyguardStateController.isKeyguardGoingAway
+            && !statusBarStateController.leaveOpenOnKeyguardHide()) {
             prepareForInWindowLauncherAnimations()
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 7becc82..4d59f1a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -911,12 +911,12 @@
                         RemoteAnimationTarget[] wallpapers,
                         RemoteAnimationTarget[] nonApps,
                         IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
+                    setOccluded(false /* isOccluded */, true /* animate */);
+
                     if (apps == null || apps.length == 0 || apps[0] == null) {
                         Log.d(TAG, "No apps provided to unocclude runner; "
                                 + "skipping animation and unoccluding.");
-
                         finishedCallback.onAnimationFinished();
-                        setOccluded(false /* isOccluded */, true /* animate */);
                         return;
                     }
 
@@ -961,7 +961,6 @@
                             @Override
                             public void onAnimationEnd(Animator animation) {
                                 try {
-                                    setOccluded(false /* isOccluded */, true /* animate */);
                                     finishedCallback.onAnimationFinished();
                                     mUnoccludeAnimator = null;
                                 } catch (RemoteException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 3bb4e64..3c373f4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -70,6 +70,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.Region;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -91,6 +92,8 @@
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.InternalInsetsInfo;
+import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 import android.view.WindowManager;
@@ -121,6 +124,7 @@
 import com.android.systemui.navigationbar.buttons.DeadZone;
 import com.android.systemui.navigationbar.buttons.KeyButtonView;
 import com.android.systemui.navigationbar.buttons.RotationContextButton;
+import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
 import com.android.systemui.navigationbar.gestural.QuickswitchOrientedNavHandle;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
@@ -153,6 +157,7 @@
 
 import java.io.PrintWriter;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Optional;
 import java.util.function.Consumer;
 
@@ -199,12 +204,14 @@
     private final Optional<Recents> mRecentsOptional;
     private final DeviceConfigProxy mDeviceConfigProxy;
     private final NavigationBarTransitions mNavigationBarTransitions;
+    private final EdgeBackGestureHandler mEdgeBackGestureHandler;
     private final Optional<BackAnimation> mBackAnimation;
     private final Handler mHandler;
     private final UiEventLogger mUiEventLogger;
     private final NavBarHelper mNavBarHelper;
     private final NotificationShadeDepthController mNotificationShadeDepthController;
     private final UserContextProvider mUserContextProvider;
+    private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener;
     private NavigationBarFrame mFrame;
 
     private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
@@ -516,6 +523,7 @@
             DeadZone deadZone,
             DeviceConfigProxy deviceConfigProxy,
             NavigationBarTransitions navigationBarTransitions,
+            EdgeBackGestureHandler edgeBackGestureHandler,
             Optional<BackAnimation> backAnimation,
             UserContextProvider userContextProvider) {
         super(navigationBarView);
@@ -542,6 +550,7 @@
         mDeadZone = deadZone;
         mDeviceConfigProxy = deviceConfigProxy;
         mNavigationBarTransitions = navigationBarTransitions;
+        mEdgeBackGestureHandler = edgeBackGestureHandler;
         mBackAnimation = backAnimation;
         mHandler = mainHandler;
         mUiEventLogger = uiEventLogger;
@@ -555,6 +564,29 @@
         mInputMethodManager = inputMethodManager;
         mUserContextProvider = userContextProvider;
 
+        mOnComputeInternalInsetsListener = info -> {
+            // When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully
+            // gestural mode, the entire nav bar should be touchable.
+            if (!mEdgeBackGestureHandler.isHandlingGestures()) {
+                // We're in 2/3 button mode OR back button force-shown in SUW
+                if (!mImeVisible) {
+                    // IME not showing, take all touches
+                    info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+                }
+                if (!mView.isImeRenderingNavButtons()) {
+                    // IME showing but not drawing any buttons, take all touches
+                    info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+                }
+            }
+
+            // When in gestural and the IME is showing, don't use the nearest region since it will
+            // take gesture space away from the IME
+            info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+            info.touchableRegion.set(getButtonLocations(false /* includeFloatingButtons */,
+                    false /* inScreen */, false /* useNearestRegion */));
+        };
+
+        mView.setEdgeBackGestureHandler(mEdgeBackGestureHandler);
         mNavBarMode = mNavigationModeController.addListener(mModeChangedListener);
     }
 
@@ -569,6 +601,7 @@
         mView.setBarTransitions(mNavigationBarTransitions);
         mView.setTouchHandler(mTouchHandler);
         mView.setNavBarMode(mNavBarMode);
+        mEdgeBackGestureHandler.setStateChangeCallback(mView::updateStates);
         mView.updateRotationButton();
 
         mView.setVisibility(
@@ -646,11 +679,14 @@
         mView.setNavBarMode(mNavBarMode);
         mView.setUpdateActiveTouchRegionsCallback(
                 () -> mOverviewProxyService.onActiveNavBarRegionChanges(
-                        mView.getButtonLocations(
+                        getButtonLocations(
                                 true /* includeFloatingButtons */,
                                 true /* inScreen */,
                                 true /* useNearestRegion */)));
 
+        mView.getViewTreeObserver().addOnComputeInternalInsetsListener(
+                mOnComputeInternalInsetsListener);
+
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
 
         mPipOptional.ifPresent(mView::addPipExclusionBoundsChangeListener);
@@ -721,6 +757,8 @@
             mOrientationHandle.getViewTreeObserver().removeOnGlobalLayoutListener(
                     mOrientationHandleGlobalLayoutListener);
         }
+        mView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
+                mOnComputeInternalInsetsListener);
         mHandler.removeCallbacks(mAutoDim);
         mHandler.removeCallbacks(mOnVariableDurationHomeLongClick);
         mHandler.removeCallbacks(mEnableLayoutTransitions);
@@ -1044,8 +1082,8 @@
     }
 
     private void handleTransientChanged() {
-        mView.onTransientStateChanged(mTransientShown,
-                mTransientShownFromGestureOnSystemBar);
+        mEdgeBackGestureHandler.onNavBarTransientStateChanged(mTransientShown);
+
         final int transitionMode = transitionMode(mTransientShown, mAppearance);
         if (updateTransitionMode(transitionMode) && mLightBarController != null) {
             mLightBarController.onNavigationBarModeChanged(transitionMode);
@@ -1639,6 +1677,79 @@
         mNavigationIconHints = hints;
     }
 
+    /**
+     * @param includeFloatingButtons Whether to include the floating rotation and overlay button in
+     *                               the region for all the buttons
+     * @param inScreenSpace Whether to return values in screen space or window space
+     * @param useNearestRegion Whether to use the nearest region instead of the actual button bounds
+     * @return
+     */
+    Region getButtonLocations(boolean includeFloatingButtons, boolean inScreenSpace,
+            boolean useNearestRegion) {
+        if (useNearestRegion && !inScreenSpace) {
+            // We currently don't support getting the nearest region in anything but screen space
+            useNearestRegion = false;
+        }
+        Region region = new Region();
+        Map<View, Rect> touchRegionCache = mView.getButtonTouchRegionCache();
+        updateButtonLocation(
+                region, touchRegionCache, mView.getBackButton(), inScreenSpace, useNearestRegion);
+        updateButtonLocation(
+                region, touchRegionCache, mView.getHomeButton(), inScreenSpace, useNearestRegion);
+        updateButtonLocation(region, touchRegionCache, mView.getRecentsButton(), inScreenSpace,
+                useNearestRegion);
+        updateButtonLocation(region, touchRegionCache, mView.getImeSwitchButton(), inScreenSpace,
+                useNearestRegion);
+        updateButtonLocation(
+                region, touchRegionCache, mView.getAccessibilityButton(), inScreenSpace,
+                useNearestRegion);
+        if (includeFloatingButtons && mView.getFloatingRotationButton().isVisible()) {
+            // Note: this button is floating so the nearest region doesn't apply
+            updateButtonLocation(
+                    region, mView.getFloatingRotationButton().getCurrentView(), inScreenSpace);
+        } else {
+            updateButtonLocation(region, touchRegionCache, mView.getRotateSuggestionButton(),
+                    inScreenSpace, useNearestRegion);
+        }
+        return region;
+    }
+
+    private void updateButtonLocation(
+            Region region,
+            Map<View, Rect> touchRegionCache,
+            ButtonDispatcher button,
+            boolean inScreenSpace,
+            boolean useNearestRegion) {
+        if (button == null) {
+            return;
+        }
+        View view = button.getCurrentView();
+        if (view == null || !button.isVisible()) {
+            return;
+        }
+        // If the button is tappable from perspective of NearestTouchFrame, then we'll
+        // include the regions where the tap is valid instead of just the button layout location
+        if (useNearestRegion && touchRegionCache.containsKey(view)) {
+            region.op(touchRegionCache.get(view), Region.Op.UNION);
+            return;
+        }
+        updateButtonLocation(region, view, inScreenSpace);
+    }
+
+    private void updateButtonLocation(Region region, View view, boolean inScreenSpace) {
+        Rect bounds = new Rect();
+        if (inScreenSpace) {
+            view.getBoundsOnScreen(bounds);
+        } else {
+            int[] location = new int[2];
+            view.getLocationInWindow(location);
+            bounds.set(location[0], location[1],
+                    location[0] + view.getWidth(),
+                    location[1] + view.getHeight());
+        }
+        region.op(bounds, Region.Op.UNION);
+    }
+
     private final ModeChangedListener mModeChangedListener = new ModeChangedListener() {
         @Override
         public void onNavigationModeChanged(int mode) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
index 93bf136..f6bfd6c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
@@ -24,6 +24,7 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
+import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
 
 import dagger.Module;
 import dagger.Provides;
@@ -55,6 +56,14 @@
         return barView.findViewById(R.id.navigation_bar_view);
     }
 
+    /** */
+    @Provides
+    @NavigationBarScope
+    static EdgeBackGestureHandler provideEdgeBackGestureHandler(
+            EdgeBackGestureHandler.Factory factory, @DisplayId Context context) {
+        return factory.create(context);
+    }
+
     /** A WindowManager specific to the display's context. */
     @Provides
     @NavigationBarScope
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 6e00ebc..a13c199 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -41,8 +41,6 @@
 import android.graphics.Canvas;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.Region.Op;
 import android.os.Bundle;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -53,8 +51,6 @@
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver.InternalInsetsInfo;
-import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
 import android.view.WindowInsets;
 import android.view.WindowInsetsController.Behavior;
 import android.view.WindowManager;
@@ -93,7 +89,6 @@
 import com.android.wm.shell.pip.Pip;
 
 import java.io.PrintWriter;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.Executor;
@@ -123,11 +118,6 @@
     private int mNavBarMode;
     private boolean mImeDrawsImeNavBar;
 
-    private final Region mTmpRegion = new Region();
-    private final int[] mTmpPosition = new int[2];
-    private Rect mTmpBounds = new Rect();
-    private Map<View, Rect> mButtonFullTouchableRegions = new HashMap<>();
-
     private KeyButtonDrawable mBackIcon;
     private KeyButtonDrawable mHomeDefaultIcon;
     private KeyButtonDrawable mRecentIcon;
@@ -138,7 +128,6 @@
 
     private EdgeBackGestureHandler mEdgeBackGestureHandler;
     private final DeadZone mDeadZone;
-    private boolean mDeadZoneConsuming = false;
     private NavigationBarTransitions mBarTransitions;
     @Nullable
     private AutoHideController mAutoHideController;
@@ -152,7 +141,6 @@
     private boolean mUseCarModeUi = false;
     private boolean mInCarMode = false;
     private boolean mDockedStackExists;
-    private boolean mImeVisible;
     private boolean mScreenOn = true;
 
     private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>();
@@ -272,31 +260,6 @@
                 }
             };
 
-    private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> {
-        // When the nav bar is in 2-button or 3-button mode, or when the back button is force-shown
-        // while in gesture nav in SUW, the entire nav bar should be touchable.
-        if (!mEdgeBackGestureHandler.isHandlingGestures()) {
-            // We're in 2/3 button mode OR back button force-shown in SUW
-            if (!mImeVisible) {
-                // IME not showing, take all touches
-                info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
-                return;
-            }
-  
-            if (!isImeRenderingNavButtons()) {
-                // IME showing but not drawing any buttons, take all touches
-                info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
-                return;     
-            }
-        }
-
-        // When in gestural and the IME is showing, don't use the nearest region since it will take
-        // gesture space away from the IME
-        info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-        info.touchableRegion.set(getButtonLocations(false /* includeFloatingButtons */,
-                false /* inScreen */, false /* useNearestRegion */));
-    };
-
     private final RotationButtonUpdatesCallback mRotationButtonListener =
             new RotationButtonUpdatesCallback() {
                 @Override
@@ -315,13 +278,6 @@
                 }
             };
 
-    private final Consumer<Boolean> mNavbarOverlayVisibilityChangeCallback = (visible) -> {
-        if (visible && mAutoHideController != null) {
-            mAutoHideController.touchAutoHide();
-        }
-        notifyActiveTouchRegions();
-    };
-
     public NavigationBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
@@ -380,9 +336,6 @@
 
         mNavColorSampleMargin = getResources()
                         .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
-        mEdgeBackGestureHandler = Dependency.get(EdgeBackGestureHandler.Factory.class)
-                .create(mContext);
-        mEdgeBackGestureHandler.setStateChangeCallback(this::updateStates);
         Executor backgroundExecutor = Dependency.get(Dependency.BACKGROUND_EXECUTOR);
         mRegionSamplingHelper = new RegionSamplingHelper(this,
                 new RegionSamplingHelper.SamplingCallback() {
@@ -409,6 +362,10 @@
                 }, backgroundExecutor);
     }
 
+    public void setEdgeBackGestureHandler(EdgeBackGestureHandler edgeBackGestureHandler) {
+        mEdgeBackGestureHandler = edgeBackGestureHandler;
+    }
+
     void setBarTransitions(NavigationBarTransitions navigationBarTransitions) {
         mBarTransitions = navigationBarTransitions;
     }
@@ -681,8 +638,7 @@
         if (!visible) {
             mTransitionListener.onBackAltCleared();
         }
-        mImeVisible = visible;
-        mRotationButtonController.getRotationButton().setCanShowRotationButton(!mImeVisible);
+        mRotationButtonController.getRotationButton().setCanShowRotationButton(!visible);
     }
 
     void setDisabledFlags(int disabledFlags, SysUiState sysUiState) {
@@ -774,7 +730,7 @@
     /**
      * Returns whether the IME is currently visible and drawing the nav buttons.
      */
-    private boolean isImeRenderingNavButtons() {
+    boolean isImeRenderingNavButtons() {
         return mImeDrawsImeNavBar
                 && mImeCanRenderGesturalNavButtons
                 && (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0;
@@ -1003,75 +959,14 @@
         notifyActiveTouchRegions();
     }
 
-    private void updateButtonTouchRegionCache() {
+    Map<View, Rect> getButtonTouchRegionCache() {
         FrameLayout navBarLayout = mIsVertical
                 ? mNavigationInflaterView.mVertical
                 : mNavigationInflaterView.mHorizontal;
-        mButtonFullTouchableRegions = ((NearestTouchFrame) navBarLayout
+        return ((NearestTouchFrame) navBarLayout
                 .findViewById(R.id.nav_buttons)).getFullTouchableChildRegions();
     }
 
-    /**
-     * @param includeFloatingButtons Whether to include the floating rotation and overlay button in
-     *                               the region for all the buttons
-     * @param inScreenSpace Whether to return values in screen space or window space
-     * @param useNearestRegion Whether to use the nearest region instead of the actual button bounds
-     * @return
-     */
-    Region getButtonLocations(boolean includeFloatingButtons, boolean inScreenSpace,
-            boolean useNearestRegion) {
-        // TODO: move this method to NavigationBar.
-        // TODO: don't use member variables for temp storage like mTmpRegion.
-        if (useNearestRegion && !inScreenSpace) {
-            // We currently don't support getting the nearest region in anything but screen space
-            useNearestRegion = false;
-        }
-        mTmpRegion.setEmpty();
-        updateButtonTouchRegionCache();
-        updateButtonLocation(getBackButton(), inScreenSpace, useNearestRegion);
-        updateButtonLocation(getHomeButton(), inScreenSpace, useNearestRegion);
-        updateButtonLocation(getRecentsButton(), inScreenSpace, useNearestRegion);
-        updateButtonLocation(getImeSwitchButton(), inScreenSpace, useNearestRegion);
-        updateButtonLocation(getAccessibilityButton(), inScreenSpace, useNearestRegion);
-        if (includeFloatingButtons && mFloatingRotationButton.isVisible()) {
-            // Note: this button is floating so the nearest region doesn't apply
-            updateButtonLocation(mFloatingRotationButton.getCurrentView(), inScreenSpace);
-        } else {
-            updateButtonLocation(getRotateSuggestionButton(), inScreenSpace, useNearestRegion);
-        }
-        return mTmpRegion;
-    }
-
-    private void updateButtonLocation(ButtonDispatcher button, boolean inScreenSpace,
-            boolean useNearestRegion) {
-        if (button == null) {
-            return;
-        }
-        View view = button.getCurrentView();
-        if (view == null || !button.isVisible()) {
-            return;
-        }
-        // If the button is tappable from perspective of NearestTouchFrame, then we'll
-        // include the regions where the tap is valid instead of just the button layout location
-        if (useNearestRegion && mButtonFullTouchableRegions.containsKey(view)) {
-            mTmpRegion.op(mButtonFullTouchableRegions.get(view), Op.UNION);
-            return;
-        }
-        updateButtonLocation(view, inScreenSpace);
-    }
-
-    private void updateButtonLocation(View view, boolean inScreenSpace) {
-        if (inScreenSpace) {
-            view.getBoundsOnScreen(mTmpBounds);
-        } else {
-            view.getLocationInWindow(mTmpPosition);
-            mTmpBounds.set(mTmpPosition[0], mTmpPosition[1],
-                    mTmpPosition[0] + view.getWidth(),
-                    mTmpPosition[1] + view.getHeight());
-        }
-        mTmpRegion.op(mTmpBounds, Op.UNION);
-    }
-
     private void updateOrientationViews() {
         mHorizontal = findViewById(R.id.horizontal);
         mVertical = findViewById(R.id.vertical);
@@ -1272,7 +1167,6 @@
             mRotationButtonController.registerListeners();
         }
 
-        getViewTreeObserver().addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
         updateNavButtonIcons();
     }
 
@@ -1288,8 +1182,6 @@
         }
 
         mEdgeBackGestureHandler.onNavBarDetached();
-        getViewTreeObserver().removeOnComputeInternalInsetsListener(
-                mOnComputeInternalInsetsListener);
     }
 
     public void dump(PrintWriter pw) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index 772e9fa..248c78e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -147,16 +147,9 @@
         if (mController.getWalletClient().isWalletServiceAvailable()
                 && mController.getWalletClient().isWalletFeatureAvailable()) {
             if (mSelectedCard != null) {
-                if (isDeviceLocked) {
-                    state.state = Tile.STATE_INACTIVE;
-                    state.secondaryLabel =
-                            mContext.getString(R.string.wallet_secondary_label_device_locked);
-                    state.sideViewCustomDrawable = null;
-                } else {
-                    state.state = Tile.STATE_ACTIVE;
-                    state.secondaryLabel = mSelectedCard.getContentDescription();
-                    state.sideViewCustomDrawable = mCardViewDrawable;
-                }
+                state.state = isDeviceLocked ? Tile.STATE_INACTIVE : Tile.STATE_ACTIVE;
+                state.secondaryLabel = mSelectedCard.getContentDescription();
+                state.sideViewCustomDrawable = mCardViewDrawable;
             } else {
                 state.state = Tile.STATE_INACTIVE;
                 state.secondaryLabel =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index f4dd415..d99c1d1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -92,15 +92,15 @@
 
     @Override
     protected void handleClick(@Nullable View view) {
-        if (mKeyguard.isMethodSecure() && mKeyguard.isShowing()) {
-            mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
-                mSensorPrivacyController.setSensorBlocked(QS_TILE, getSensorId(),
-                        !mSensorPrivacyController.isSensorBlocked(getSensorId()));
-            });
+        boolean blocked = mSensorPrivacyController.isSensorBlocked(getSensorId());
+        if (mSensorPrivacyController.requiresAuthentication()
+                && mKeyguard.isMethodSecure()
+                && mKeyguard.isShowing()) {
+            mActivityStarter.postQSRunnableDismissingKeyguard(() ->
+                    mSensorPrivacyController.setSensorBlocked(QS_TILE, getSensorId(), !blocked));
             return;
         }
-        mSensorPrivacyController.setSensorBlocked(QS_TILE, getSensorId(),
-                !mSensorPrivacyController.isSensorBlocked(getSensorId()));
+        mSensorPrivacyController.setSensorBlocked(QS_TILE, getSensorId(), !blocked);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index dae375a..2d1d8b7 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -134,7 +134,9 @@
     override fun onClick(dialog: DialogInterface?, which: Int) {
         when (which) {
             BUTTON_POSITIVE -> {
-                if (keyguardStateController.isMethodSecure && keyguardStateController.isShowing) {
+                if (sensorPrivacyController.requiresAuthentication() &&
+                        keyguardStateController.isMethodSecure &&
+                        keyguardStateController.isShowing) {
                     keyguardDismissUtil.executeWhenUnlocked({
                         bgHandler.postDelayed({
                             disableSensorPrivacy()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index 01aa2ec..faae4bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -333,7 +333,7 @@
     }
 
     private void adjustScreenOrientation(State state) {
-        if (state.isKeyguardShowingAndNotOccluded() || state.mDozing) {
+        if (state.mBouncerShowing || state.isKeyguardShowingAndNotOccluded() || state.mDozing) {
             if (mKeyguardStateController.isKeyguardScreenRotationAllowed()) {
                 mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java
index 1e73d59..eb08f37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java
@@ -37,6 +37,11 @@
 
     void suppressSensorPrivacyReminders(int sensor, boolean suppress);
 
+    /**
+     * @return whether lock screen authentication is required to change the toggle state
+     */
+    boolean requiresAuthentication();
+
     interface Callback {
         void onSensorBlockedChanged(@Sensor int sensor, boolean blocked);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
index e4c444d..fffd839 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
@@ -37,6 +37,7 @@
     private final @NonNull SensorPrivacyManager mSensorPrivacyManager;
     private final SparseBooleanArray mSoftwareToggleState = new SparseBooleanArray();
     private final SparseBooleanArray mHardwareToggleState = new SparseBooleanArray();
+    private Boolean mRequiresAuthentication;
     private final Set<Callback> mCallbacks = new ArraySet<>();
 
     public IndividualSensorPrivacyControllerImpl(
@@ -96,6 +97,11 @@
     }
 
     @Override
+    public boolean requiresAuthentication() {
+        return mSensorPrivacyManager.requiresAuthentication();
+    }
+
+    @Override
     public void addCallback(@NonNull Callback listener) {
         mCallbacks.add(listener);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index 5b5dca3..d54de3fa 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -21,9 +21,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
-import android.content.res.TypedArray;
 import android.provider.Settings;
-import android.view.ContextThemeWrapper;
 import android.view.DisplayCutout;
 
 import com.android.internal.policy.SystemBarUtils;
@@ -35,6 +33,8 @@
 
 public class Utils {
 
+    private static Boolean sUseQsMediaPlayer = null;
+
     /**
      * Allows lambda iteration over a list. It is done in reverse order so it is safe
      * to add or remove items during the iteration.  Skips over null items.
@@ -81,9 +81,16 @@
      * Off by default, but can be disabled by setting to 0
      */
     public static boolean useQsMediaPlayer(Context context) {
-        int flag = Settings.Global.getInt(context.getContentResolver(),
-                Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1);
-        return flag > 0;
+        // TODO(b/192412820): Replace SHOW_MEDIA_ON_QUICK_SETTINGS with compile-time value
+        // Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS can't be toggled at runtime, so simply
+        // cache the first result we fetch and use that going forward. Do this to avoid unnecessary
+        // binder calls which may happen on the critical path.
+        if (sUseQsMediaPlayer == null) {
+            int flag = Settings.Global.getInt(context.getContentResolver(),
+                    Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1);
+            sUseQsMediaPlayer = flag > 0;
+        }
+        return sUseQsMediaPlayer;
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 2d8c4d5..2abc666 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -14,6 +14,7 @@
 import com.android.keyguard.KeyguardViewController
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import junit.framework.Assert.assertEquals
@@ -49,6 +50,8 @@
     private lateinit var biometricUnlockController: BiometricUnlockController
     @Mock
     private lateinit var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
+    @Mock
+    private lateinit var statusBarStateController: SysuiStatusBarStateController
 
     private lateinit var remoteAnimationTarget: RemoteAnimationTarget
 
@@ -57,7 +60,7 @@
         MockitoAnnotations.initMocks(this)
         keyguardUnlockAnimationController = KeyguardUnlockAnimationController(
             context, keyguardStateController, { keyguardViewMediator }, keyguardViewController,
-            featureFlags, { biometricUnlockController }
+            featureFlags, { biometricUnlockController }, statusBarStateController
         )
 
         `when`(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
index 06d45de..a074475 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
@@ -27,6 +27,7 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -136,6 +137,24 @@
     }
 
     @Test
+    fun testConnection_calledTwice_oldBrowserDisconnected() {
+        val oldBrowser = mock<MediaBrowser>()
+        whenever(browserFactory.create(any(), any(), any())).thenReturn(oldBrowser)
+
+        // When testConnection can connect to the service
+        setupBrowserConnection()
+        resumeBrowser.testConnection()
+
+        // And testConnection is called again
+        val newBrowser = mock<MediaBrowser>()
+        whenever(browserFactory.create(any(), any(), any())).thenReturn(newBrowser)
+        resumeBrowser.testConnection()
+
+        // Then we disconnect the old browser
+        verify(oldBrowser).disconnect()
+    }
+
+    @Test
     fun testFindRecentMedia_connectionFails_error() {
         // When findRecentMedia is called and we cannot connect
         setupBrowserFailed()
@@ -169,6 +188,24 @@
     }
 
     @Test
+    fun testFindRecentMedia_calledTwice_oldBrowserDisconnected() {
+        val oldBrowser = mock<MediaBrowser>()
+        whenever(browserFactory.create(any(), any(), any())).thenReturn(oldBrowser)
+
+        // When findRecentMedia is called and we connect
+        setupBrowserConnection()
+        resumeBrowser.findRecentMedia()
+
+        // And findRecentMedia is called again
+        val newBrowser = mock<MediaBrowser>()
+        whenever(browserFactory.create(any(), any(), any())).thenReturn(newBrowser)
+        resumeBrowser.findRecentMedia()
+
+        // Then we disconnect the old browser
+        verify(oldBrowser).disconnect()
+    }
+
+    @Test
     fun testFindRecentMedia_noChildren_error() {
         // When findRecentMedia is called and we connect, but do not get any results
         setupBrowserConnectionNoResults()
@@ -223,6 +260,24 @@
         verify(transportControls).play()
     }
 
+    @Test
+    fun testRestart_calledTwice_oldBrowserDisconnected() {
+        val oldBrowser = mock<MediaBrowser>()
+        whenever(browserFactory.create(any(), any(), any())).thenReturn(oldBrowser)
+
+        // When restart is called and we connect successfully
+        setupBrowserConnection()
+        resumeBrowser.restart()
+
+        // And restart is called again
+        val newBrowser = mock<MediaBrowser>()
+        whenever(browserFactory.create(any(), any(), any())).thenReturn(newBrowser)
+        resumeBrowser.restart()
+
+        // Then we disconnect the old browser
+        verify(oldBrowser).disconnect()
+    }
+
     /**
      * Helper function to mock a failed connection
      */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 7c30398..a526087 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -60,6 +60,7 @@
 import android.view.DisplayInfo;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowMetrics;
@@ -164,7 +165,7 @@
     @Mock
     private UiEventLogger mUiEventLogger;
     @Mock
-    EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory;
+    private ViewTreeObserver mViewTreeObserver;
     @Mock
     EdgeBackGestureHandler mEdgeBackGestureHandler;
     NavBarHelper mNavBarHelper;
@@ -199,8 +200,6 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        when(mEdgeBackGestureHandlerFactory.create(any(Context.class)))
-                .thenReturn(mEdgeBackGestureHandler);
         when(mLightBarcontrollerFactory.create(any(Context.class))).thenReturn(mLightBarController);
         when(mAutoHideControllerFactory.create(any(Context.class))).thenReturn(mAutoHideController);
         when(mNavigationBarView.getHomeButton()).thenReturn(mHomeButton);
@@ -213,6 +212,7 @@
         when(mNavigationBarTransitions.getLightTransitionsController())
                 .thenReturn(mLightBarTransitionsController);
         when(mStatusBarKeyguardViewManager.isNavBarVisible()).thenReturn(true);
+        when(mNavigationBarView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
         when(mUserContextProvider.createCurrentUserContext(any(Context.class)))
                 .thenReturn(mContext);
         setupSysuiDependency();
@@ -222,8 +222,6 @@
         mDependency.injectMockDependency(KeyguardStateController.class);
         mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController);
         mDependency.injectMockDependency(NavigationBarController.class);
-        mDependency.injectTestDependency(EdgeBackGestureHandler.Factory.class,
-                mEdgeBackGestureHandlerFactory);
         mDependency.injectTestDependency(OverviewProxyService.class, mOverviewProxyService);
         mDependency.injectTestDependency(NavigationModeController.class, mNavigationModeController);
         TestableLooper.get(this).runWithLooper(() -> {
@@ -463,6 +461,7 @@
                 mDeadZone,
                 mDeviceConfigProxyFake,
                 mNavigationBarTransitions,
+                mEdgeBackGestureHandler,
                 Optional.of(mock(BackAnimation.class)),
                 mUserContextProvider));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index c88ceac..4f6475f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -90,6 +90,7 @@
 
     private static final String CARD_ID = "card_id";
     private static final String LABEL = "QAW";
+    private static final String CARD_DESCRIPTION = "•••• 1234";
     private static final Icon CARD_IMAGE =
             Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888));
 
@@ -282,9 +283,7 @@
         mTile.handleUpdateState(state, null);
 
         assertEquals(Tile.STATE_ACTIVE, state.state);
-        assertEquals(
-                "•••• 1234",
-                state.secondaryLabel);
+        assertEquals(CARD_DESCRIPTION, state.secondaryLabel);
         assertNotNull(state.stateDescription);
         assertNotNull(state.sideViewCustomDrawable);
     }
@@ -298,11 +297,9 @@
         mTile.handleUpdateState(state, null);
 
         assertEquals(Tile.STATE_INACTIVE, state.state);
-        assertEquals(
-                mContext.getString(R.string.wallet_secondary_label_device_locked),
-                state.secondaryLabel);
+        assertEquals(CARD_DESCRIPTION, state.secondaryLabel);
         assertNotNull(state.stateDescription);
-        assertNull(state.sideViewCustomDrawable);
+        assertNotNull(state.sideViewCustomDrawable);
     }
 
     @Test
@@ -314,9 +311,7 @@
         mTile.handleUpdateState(state, null);
 
         assertEquals(Tile.STATE_ACTIVE, state.state);
-        assertEquals(
-                "•••• 1234",
-                state.secondaryLabel);
+        assertEquals(CARD_DESCRIPTION, state.secondaryLabel);
         assertNotNull(state.stateDescription);
         assertNotNull(state.sideViewCustomDrawable);
     }
@@ -426,6 +421,6 @@
     private WalletCard createWalletCard(Context context) {
         PendingIntent pendingIntent =
                 PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
-        return new WalletCard.Builder(CARD_ID, CARD_IMAGE, "•••• 1234", pendingIntent).build();
+        return new WalletCard.Builder(CARD_ID, CARD_IMAGE, CARD_DESCRIPTION, pendingIntent).build();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
index 540d291..1cce3b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
@@ -21,10 +21,13 @@
 import android.testing.AndroidTestingRunner
 
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
 
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.DeviceConfigProxyFake
+import com.android.systemui.util.Utils
+import com.android.systemui.util.mockito.any
 
 import org.junit.After
 import org.junit.Assert.assertFalse
@@ -32,28 +35,33 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoSession
+import org.mockito.quality.Strictness
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class NotificationSectionsFeatureManagerTest : SysuiTestCase() {
     var manager: NotificationSectionsFeatureManager? = null
     val proxyFake = DeviceConfigProxyFake()
-    var originalQsMediaPlayer: Int = 0
+    private lateinit var staticMockSession: MockitoSession
 
     @Before
     public fun setup() {
         manager = NotificationSectionsFeatureManager(proxyFake, mContext)
         manager!!.clearCache()
-        originalQsMediaPlayer = Settings.Global.getInt(context.getContentResolver(),
-                Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1)
+        staticMockSession = ExtendedMockito.mockitoSession()
+            .mockStatic<Utils>(Utils::class.java)
+            .strictness(Strictness.LENIENT)
+            .startMocking()
+        `when`(Utils.useQsMediaPlayer(any())).thenReturn(false)
         Settings.Global.putInt(context.getContentResolver(),
                 Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 0)
     }
 
     @After
     public fun teardown() {
-        Settings.Global.putInt(context.getContentResolver(),
-                Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, originalQsMediaPlayer)
+        staticMockSession.finishMocking()
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
index dc101f3..3a85972 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -233,7 +233,7 @@
 
     @Test
     public void hideSilentNotificationsPerUserSettingWithHighPriorityParent() {
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
         mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
         mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
         GroupEntry parent = new GroupEntryBuilder()
@@ -255,7 +255,7 @@
 
     @Test
     public void hideSilentNotificationsPerUserSetting() {
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
         mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
         mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
         mEntry = new NotificationEntryBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
index 26199d53..c402d2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
@@ -26,6 +26,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -309,4 +310,17 @@
         });
         verify(mWindowManager).updateViewLayout(any(), any());
     }
+
+    @Test
+    public void bouncerShowing_OrientationNoSensor() {
+        mNotificationShadeWindowController.setKeyguardShowing(true);
+        mNotificationShadeWindowController.setKeyguardOccluded(true);
+        mNotificationShadeWindowController.setBouncerShowing(true);
+        when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false);
+        mNotificationShadeWindowController.onConfigChanged(new Configuration());
+
+        verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), mLayoutParameters.capture());
+        assertThat(mLayoutParameters.getValue().screenOrientation)
+                .isEqualTo(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
+    }
 }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index f18d13d..8e32a7a 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1559,9 +1559,18 @@
                 Slog.e(TAG, "Error sending input show up notification", e);
             }
         }
+    }
+
+    // AutoFillUiCallback
+    @Override
+    public void requestFallbackFromFillDialog() {
+        setFillDialogDisabled();
         synchronized (mLock) {
-            // stop to show fill dialog
-            mSessionFlags.mFillDialogDisabled = true;
+            if (mCurrentViewId == null) {
+                return;
+            }
+            final ViewState currentView = mViewStates.get(mCurrentViewId);
+            currentView.maybeCallOnFillReady(mFlags);
         }
     }
 
@@ -3208,17 +3217,20 @@
             return;
         }
 
-        if (requestShowFillDialog(response, filledId, filterText, flags)) {
-            synchronized (mLock) {
-                final ViewState currentView = mViewStates.get(mCurrentViewId);
-                currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN);
-                mService.logDatasetShown(id, mClientState, UI_TYPE_DIALOG);
+        final AutofillId[] ids = response.getFillDialogTriggerIds();
+        if (ids != null && ArrayUtils.contains(ids, filledId)) {
+            if (requestShowFillDialog(response, filledId, filterText, flags)) {
+                synchronized (mLock) {
+                    final ViewState currentView = mViewStates.get(mCurrentViewId);
+                    currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN);
+                    mService.logDatasetShown(id, mClientState, UI_TYPE_DIALOG);
+                }
+                return;
+            } else {
+                setFillDialogDisabled();
             }
-            return;
         }
 
-        setFillDialogDisabled();
-
         if (response.supportsInlineSuggestions()) {
             synchronized (mLock) {
                 if (requestShowInlineSuggestionsLocked(response, filterText)) {
@@ -3324,15 +3336,11 @@
             return false;
         }
 
-        final AutofillId[] ids = response.getFillDialogTriggerIds();
-        if (ids == null || !ArrayUtils.contains(ids, filledId)) {
-            return false;
-        }
-
         final Drawable serviceIcon = getServiceIcon();
 
         getUiForShowing().showFillDialog(filledId, response, filterText,
-                mService.getServicePackageName(), mComponentName, serviceIcon, this);
+                mService.getServicePackageName(), mComponentName, serviceIcon, this,
+                id, mCompatMode);
         return true;
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 57768ef..3ab873d 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -97,6 +97,7 @@
         void dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent);
         void cancelSession();
         void requestShowSoftInput(AutofillId id);
+        void requestFallbackFromFillDialog();
     }
 
     public AutoFillUI(@NonNull Context context) {
@@ -388,13 +389,19 @@
     public void showFillDialog(@NonNull AutofillId focusedId, @NonNull FillResponse response,
             @Nullable String filterText, @Nullable String servicePackageName,
             @NonNull ComponentName componentName, @Nullable Drawable serviceIcon,
-            @NonNull AutoFillUiCallback callback) {
+            @NonNull AutoFillUiCallback callback, int sessionId, boolean compatMode) {
         if (sVerbose) {
             Slog.v(TAG, "showFillDialog for "
                     + componentName.toShortString() + ": " + response);
         }
 
-        // TODO: enable LogMaker
+        final LogMaker log = Helper
+                .newLogMaker(MetricsEvent.AUTOFILL_FILL_UI, componentName, servicePackageName,
+                        sessionId, compatMode)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN,
+                        filterText == null ? 0 : filterText.length())
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
+                        response.getDatasets() == null ? 0 : response.getDatasets().size());
 
         mHandler.post(() -> {
             if (callback != mCallback) {
@@ -406,6 +413,7 @@
                     mUiModeMgr.isNightMode(), new DialogFillUi.UiCallback() {
                         @Override
                         public void onResponsePicked(FillResponse response) {
+                            log(MetricsEvent.TYPE_DETAIL);
                             hideFillDialogUiThread(callback);
                             if (mCallback != null) {
                                 mCallback.authenticate(response.getRequestId(),
@@ -417,6 +425,7 @@
 
                         @Override
                         public void onDatasetPicked(Dataset dataset) {
+                            log(MetricsEvent.TYPE_ACTION);
                             hideFillDialogUiThread(callback);
                             if (mCallback != null) {
                                 final int datasetIndex = response.getDatasets().indexOf(dataset);
@@ -426,15 +435,29 @@
                         }
 
                         @Override
-                        public void onCanceled() {
+                        public void onDismissed() {
+                            log(MetricsEvent.TYPE_DISMISS);
                             hideFillDialogUiThread(callback);
                             callback.requestShowSoftInput(focusedId);
                         }
 
                         @Override
+                        public void onCanceled() {
+                            log(MetricsEvent.TYPE_CLOSE);
+                            hideFillDialogUiThread(callback);
+                            callback.requestShowSoftInput(focusedId);
+                            callback.requestFallbackFromFillDialog();
+                        }
+
+                        @Override
                         public void startIntentSender(IntentSender intentSender) {
                             mCallback.startIntentSenderAndFinishSession(intentSender);
                         }
+
+                        private void log(int type) {
+                            log.setType(type);
+                            mMetricsLogger.write(log);
+                        }
                     });
         });
     }
diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
index f9f5289..5a1a1ae 100644
--- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
@@ -81,6 +81,7 @@
     interface UiCallback {
         void onResponsePicked(@NonNull FillResponse response);
         void onDatasetPicked(@NonNull Dataset dataset);
+        void onDismissed();
         void onCanceled();
         void startIntentSender(IntentSender intentSender);
     }
@@ -144,6 +145,7 @@
         mDialog = new Dialog(mContext, mThemeId);
         mDialog.setContentView(decor);
         setDialogParamsAsBottomSheet();
+        mDialog.setOnCancelListener((d) -> mCallback.onCanceled());
 
         show();
     }
@@ -220,7 +222,7 @@
         final TextView noButton = decor.findViewById(R.id.autofill_dialog_no);
         // set "No thinks" by default
         noButton.setText(R.string.autofill_save_no);
-        noButton.setOnClickListener((v) -> mCallback.onCanceled());
+        noButton.setOnClickListener((v) -> mCallback.onDismissed());
     }
 
     private void setContinueButton(View decor, View.OnClickListener listener) {
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 9d4b50b..80182d2 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -22,6 +22,7 @@
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
 import android.hardware.input.InputManagerInternal;
 import android.hardware.input.VirtualKeyEvent;
@@ -29,11 +30,13 @@
 import android.hardware.input.VirtualMouseRelativeEvent;
 import android.hardware.input.VirtualMouseScrollEvent;
 import android.hardware.input.VirtualTouchEvent;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.view.Display;
+import android.view.InputDevice;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -44,7 +47,11 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
 
 /** Controls virtual input devices, including device lifecycle and event dispatch. */
 class InputController {
@@ -72,20 +79,27 @@
     @GuardedBy("mLock")
     final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>();
 
+    private final Handler mHandler;
     private final NativeWrapper mNativeWrapper;
     private final DisplayManagerInternal mDisplayManagerInternal;
     private final InputManagerInternal mInputManagerInternal;
+    private final DeviceCreationThreadVerifier mThreadVerifier;
 
-    InputController(@NonNull Object lock) {
-        this(lock, new NativeWrapper());
+    InputController(@NonNull Object lock, @NonNull Handler handler) {
+        this(lock, new NativeWrapper(), handler,
+                // Verify that virtual devices are not created on the handler thread.
+                () -> !handler.getLooper().isCurrentThread());
     }
 
     @VisibleForTesting
-    InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper) {
+    InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper,
+            @NonNull Handler handler, @NonNull DeviceCreationThreadVerifier threadVerifier) {
         mLock = lock;
+        mHandler = handler;
         mNativeWrapper = nativeWrapper;
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
+        mThreadVerifier = threadVerifier;
     }
 
     void close() {
@@ -108,23 +122,13 @@
             @NonNull IBinder deviceToken,
             int displayId) {
         final String phys = createPhys(PHYS_TYPE_KEYBOARD);
-        setUniqueIdAssociation(displayId, phys);
-        final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys);
-        if (fd < 0) {
-            throw new RuntimeException(
-                    "A native error occurred when creating keyboard: " + -fd);
-        }
-        final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken);
-        synchronized (mLock) {
-            mInputDeviceDescriptors.put(deviceToken,
-                    new InputDeviceDescriptor(fd, binderDeathRecipient,
-                            InputDeviceDescriptor.TYPE_KEYBOARD, displayId, phys));
-        }
         try {
-            deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
-        } catch (RemoteException e) {
-            // TODO(b/215608394): remove and close InputDeviceDescriptor
-            throw new RuntimeException("Could not create virtual keyboard", e);
+            createDeviceInternal(InputDeviceDescriptor.TYPE_KEYBOARD, deviceName, vendorId,
+                    productId, deviceToken, displayId, phys,
+                    () -> mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys));
+        } catch (DeviceCreationException e) {
+            throw new RuntimeException(
+                    "Failed to create virtual keyboard device '" + deviceName + "'.", e);
         }
     }
 
@@ -134,25 +138,15 @@
             @NonNull IBinder deviceToken,
             int displayId) {
         final String phys = createPhys(PHYS_TYPE_MOUSE);
-        setUniqueIdAssociation(displayId, phys);
-        final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys);
-        if (fd < 0) {
-            throw new RuntimeException(
-                    "A native error occurred when creating mouse: " + -fd);
-        }
-        final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken);
-        synchronized (mLock) {
-            mInputDeviceDescriptors.put(deviceToken,
-                    new InputDeviceDescriptor(fd, binderDeathRecipient,
-                            InputDeviceDescriptor.TYPE_MOUSE, displayId, phys));
-            mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
-        }
         try {
-            deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
-        } catch (RemoteException e) {
-            // TODO(b/215608394): remove and close InputDeviceDescriptor
-            throw new RuntimeException("Could not create virtual mouse", e);
+            createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId,
+                    deviceToken, displayId, phys,
+                    () -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys));
+        } catch (DeviceCreationException e) {
+            throw new RuntimeException(
+                    "Failed to create virtual mouse device: '" + deviceName + "'.", e);
         }
+        mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
     }
 
     void createTouchscreen(@NonNull String deviceName,
@@ -162,24 +156,14 @@
             int displayId,
             @NonNull Point screenSize) {
         final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN);
-        setUniqueIdAssociation(displayId, phys);
-        final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, phys,
-                screenSize.y, screenSize.x);
-        if (fd < 0) {
-            throw new RuntimeException(
-                    "A native error occurred when creating touchscreen: " + -fd);
-        }
-        final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken);
-        synchronized (mLock) {
-            mInputDeviceDescriptors.put(deviceToken,
-                    new InputDeviceDescriptor(fd, binderDeathRecipient,
-                            InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId, phys));
-        }
         try {
-            deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
-        } catch (RemoteException e) {
-            // TODO(b/215608394): remove and close InputDeviceDescriptor
-            throw new RuntimeException("Could not create virtual touchscreen", e);
+            createDeviceInternal(InputDeviceDescriptor.TYPE_TOUCHSCREEN, deviceName, vendorId,
+                    productId, deviceToken, displayId, phys,
+                    () -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
+                            phys, screenSize.y, screenSize.x));
+        } catch (DeviceCreationException e) {
+            throw new RuntimeException(
+                    "Failed to create virtual touchscreen device '" + deviceName + "'.", e);
         }
     }
 
@@ -510,4 +494,133 @@
             unregisterInputDevice(mDeviceToken);
         }
     }
+
+    /** A helper class used to wait for an input device to be registered. */
+    private class WaitForDevice implements AutoCloseable {
+        private final CountDownLatch mDeviceAddedLatch = new CountDownLatch(1);
+        private final InputManager.InputDeviceListener mListener;
+
+        WaitForDevice(String deviceName, int vendorId, int productId) {
+            mListener = new InputManager.InputDeviceListener() {
+                @Override
+                public void onInputDeviceAdded(int deviceId) {
+                    final InputDevice device = InputManager.getInstance().getInputDevice(
+                            deviceId);
+                    Objects.requireNonNull(device, "Newly added input device was null.");
+                    if (!device.getName().equals(deviceName)) {
+                        return;
+                    }
+                    final InputDeviceIdentifier id = device.getIdentifier();
+                    if (id.getVendorId() != vendorId || id.getProductId() != productId) {
+                        return;
+                    }
+                    mDeviceAddedLatch.countDown();
+                }
+
+                @Override
+                public void onInputDeviceRemoved(int deviceId) {
+
+                }
+
+                @Override
+                public void onInputDeviceChanged(int deviceId) {
+
+                }
+            };
+            InputManager.getInstance().registerInputDeviceListener(mListener, mHandler);
+        }
+
+        /** Note: This must not be called from {@link #mHandler}'s thread. */
+        void waitForDeviceCreation() throws DeviceCreationException {
+            try {
+                if (!mDeviceAddedLatch.await(1, TimeUnit.MINUTES)) {
+                    throw new DeviceCreationException(
+                            "Timed out waiting for virtual device to be created.");
+                }
+            } catch (InterruptedException e) {
+                throw new DeviceCreationException(
+                        "Interrupted while waiting for virtual device to be created.", e);
+            }
+        }
+
+        @Override
+        public void close() {
+            InputManager.getInstance().unregisterInputDeviceListener(mListener);
+        }
+    }
+
+    /** An internal exception that is thrown to indicate an error when opening a virtual device. */
+    private static class DeviceCreationException extends Exception {
+        DeviceCreationException(String message) {
+            super(message);
+        }
+        DeviceCreationException(String message, Exception cause) {
+            super(message, cause);
+        }
+    }
+
+    /**
+     * Creates a virtual input device synchronously, and waits for the notification that the device
+     * was added.
+     *
+     * Note: Input device creation is expected to happen on a binder thread, and the calling thread
+     * will be blocked until the input device creation is successful. This should not be called on
+     * the handler's thread.
+     *
+     * @throws DeviceCreationException Throws this exception if anything unexpected happens in the
+     *                                 process of creating the device. This method will take care
+     *                                 to restore the state of the system in the event of any
+     *                                 unexpected behavior.
+     */
+    private void createDeviceInternal(@InputDeviceDescriptor.Type int type, String deviceName,
+            int vendorId, int productId, IBinder deviceToken, int displayId, String phys,
+            Supplier<Integer> deviceOpener)
+            throws DeviceCreationException {
+        if (!mThreadVerifier.isValidThread()) {
+            throw new IllegalStateException(
+                    "Virtual device creation should happen on an auxiliary thread (e.g. binder "
+                            + "thread) and not from the handler's thread.");
+        }
+
+        final int fd;
+        final BinderDeathRecipient binderDeathRecipient;
+
+        setUniqueIdAssociation(displayId, phys);
+        try (WaitForDevice waiter = new WaitForDevice(deviceName, vendorId, productId)) {
+            fd = deviceOpener.get();
+            if (fd < 0) {
+                throw new DeviceCreationException(
+                        "A native error occurred when creating touchscreen: " + -fd);
+            }
+            // The fd is valid from here, so ensure that all failures close the fd after this point.
+            try {
+                waiter.waitForDeviceCreation();
+
+                binderDeathRecipient = new BinderDeathRecipient(deviceToken);
+                try {
+                    deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
+                } catch (RemoteException e) {
+                    throw new DeviceCreationException(
+                            "Client died before virtual device could be created.", e);
+                }
+            } catch (DeviceCreationException e) {
+                mNativeWrapper.closeUinput(fd);
+                throw e;
+            }
+        } catch (DeviceCreationException e) {
+            InputManager.getInstance().removeUniqueIdAssociation(phys);
+            throw e;
+        }
+
+        synchronized (mLock) {
+            mInputDeviceDescriptors.put(deviceToken,
+                    new InputDeviceDescriptor(fd, binderDeathRecipient, type, displayId, phys));
+        }
+    }
+
+    @VisibleForTesting
+    interface DeviceCreationThreadVerifier {
+        /** Returns true if the calling thread is a valid thread for device creation. */
+        boolean isValidThread();
+    }
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index de14ef6..9802b97 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -166,7 +166,8 @@
         mAppToken = token;
         mParams = params;
         if (inputController == null) {
-            mInputController = new InputController(mVirtualDeviceLock);
+            mInputController = new InputController(
+                    mVirtualDeviceLock, context.getMainThreadHandler());
         } else {
             mInputController = inputController;
         }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 8f37823..bc40170 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3055,19 +3055,7 @@
             return true;
         }
 
-        if (packageName == null) {
-            return false;
-        }
-
-        final int packageUid = mPmInternal.getPackageUid(packageName,
-                PackageManager.MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getUserId(callerUid));
-
-        if (DEBUG_OBB) {
-            Slog.d(TAG, "packageName = " + packageName + ", packageUid = " +
-                    packageUid + ", callerUid = " + callerUid);
-        }
-
-        return callerUid == packageUid;
+        return mPmInternal.isSameApp(packageName, callerUid, UserHandle.getUserId(callerUid));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java
index 6e28d8f..5a234f5 100644
--- a/services/core/java/com/android/server/am/AppBatteryTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -56,7 +56,6 @@
 import android.os.BatteryStatsInternal;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
-import android.os.Build;
 import android.os.PowerExemptionManager;
 import android.os.PowerExemptionManager.ReasonCode;
 import android.os.SystemClock;
@@ -97,7 +96,7 @@
     static final boolean DEBUG_BACKGROUND_BATTERY_TRACKER = false;
 
     static final boolean DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE =
-            DEBUG_BACKGROUND_BATTERY_TRACKER | Build.IS_DEBUGGABLE;
+            DEBUG_BACKGROUND_BATTERY_TRACKER | false;
 
     // As we don't support realtime per-UID battery usage stats yet, we're polling the stats
     // in a regular time basis.
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 8ab0b93..5ae3f33 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -164,6 +164,7 @@
     private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
     private static final int MSG_RELOAD_DEVICE_ALIASES = 5;
     private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 6;
+    private static final int MSG_POINTER_DISPLAY_ID_CHANGED = 7;
 
     private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
 
@@ -276,11 +277,24 @@
     @GuardedBy("mAssociationLock")
     private final Map<String, String> mUniqueIdAssociations = new ArrayMap<>();
 
+    // Guards per-display input properties and properties relating to the mouse pointer.
+    // Threads can wait on this lock to be notified the next time the display on which the mouse
+    // pointer is shown has changed.
     private final Object mAdditionalDisplayInputPropertiesLock = new Object();
 
-    // Forces the MouseCursorController to target a specific display id.
+    // Forces the PointerController to target a specific display id.
     @GuardedBy("mAdditionalDisplayInputPropertiesLock")
     private int mOverriddenPointerDisplayId = Display.INVALID_DISPLAY;
+
+    // PointerController is the source of truth of the pointer display. This is the value of the
+    // latest pointer display id reported by PointerController.
+    @GuardedBy("mAdditionalDisplayInputPropertiesLock")
+    private int mAcknowledgedPointerDisplayId = Display.INVALID_DISPLAY;
+    // This is the latest display id that IMS has requested PointerController to use. If there are
+    // no devices that can control the pointer, PointerController may end up disregarding this
+    // value.
+    @GuardedBy("mAdditionalDisplayInputPropertiesLock")
+    private int mRequestedPointerDisplayId = Display.INVALID_DISPLAY;
     @GuardedBy("mAdditionalDisplayInputPropertiesLock")
     private final SparseArray<AdditionalDisplayInputProperties> mAdditionalDisplayInputProperties =
             new SparseArray<>();
@@ -289,7 +303,6 @@
     @GuardedBy("mAdditionalDisplayInputPropertiesLock")
     private PointerIcon mIcon;
 
-
     // Holds all the registered gesture monitors that are implemented as spy windows. The spy
     // windows are mapped by their InputChannel tokens.
     @GuardedBy("mInputMonitors")
@@ -383,6 +396,10 @@
         NativeInputManagerService getNativeService(InputManagerService service) {
             return new NativeInputManagerService.NativeImpl(service, mContext, mLooper.getQueue());
         }
+
+        void registerLocalService(InputManagerInternal localService) {
+            LocalServices.addService(InputManagerInternal.class, localService);
+        }
     }
 
     public InputManagerService(Context context) {
@@ -391,11 +408,14 @@
 
     @VisibleForTesting
     InputManagerService(Injector injector) {
+        // The static association map is accessed by both java and native code, so it must be
+        // initialized before initializing the native service.
+        mStaticAssociations = loadStaticInputPortAssociations();
+
         mContext = injector.getContext();
         mHandler = new InputManagerHandler(injector.getLooper());
         mNative = injector.getNativeService(this);
 
-        mStaticAssociations = loadStaticInputPortAssociations();
         mUseDevInputEventForAudioJack =
                 mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
         Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
@@ -406,7 +426,7 @@
         mDoubleTouchGestureEnableFile = TextUtils.isEmpty(doubleTouchGestureEnablePath) ? null :
             new File(doubleTouchGestureEnablePath);
 
-        LocalServices.addService(InputManagerInternal.class, new LocalService());
+        injector.registerLocalService(new LocalService());
     }
 
     public void setWindowManagerCallbacks(WindowManagerCallbacks callbacks) {
@@ -556,6 +576,8 @@
                 vArray[i] = viewports.get(i);
             }
             mNative.setDisplayViewports(vArray);
+            // Always attempt to update the pointer display when viewports change.
+            updatePointerDisplayId();
 
             if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) {
                 final AdditionalDisplayInputProperties properties =
@@ -1961,10 +1983,43 @@
         return result;
     }
 
-    private void setVirtualMousePointerDisplayId(int displayId) {
+    /**
+     * Update the display on which the mouse pointer is shown.
+     * If there is an overridden display for the mouse pointer, use that. Otherwise, query
+     * WindowManager for the pointer display.
+     *
+     * @return true if the pointer displayId changed, false otherwise.
+     */
+    private boolean updatePointerDisplayId() {
+        synchronized (mAdditionalDisplayInputPropertiesLock) {
+            final int pointerDisplayId = mOverriddenPointerDisplayId != Display.INVALID_DISPLAY
+                    ? mOverriddenPointerDisplayId : mWindowManagerCallbacks.getPointerDisplayId();
+            if (mRequestedPointerDisplayId == pointerDisplayId) {
+                return false;
+            }
+            mRequestedPointerDisplayId = pointerDisplayId;
+            mNative.setPointerDisplayId(pointerDisplayId);
+            return true;
+        }
+    }
+
+    private void handlePointerDisplayIdChanged(PointerDisplayIdChangedArgs args) {
+        synchronized (mAdditionalDisplayInputPropertiesLock) {
+            mAcknowledgedPointerDisplayId = args.mPointerDisplayId;
+            // Notify waiting threads that the display of the mouse pointer has changed.
+            mAdditionalDisplayInputPropertiesLock.notifyAll();
+        }
+        mWindowManagerCallbacks.notifyPointerDisplayIdChanged(
+                args.mPointerDisplayId, args.mXPosition, args.mYPosition);
+    }
+
+    private boolean setVirtualMousePointerDisplayIdBlocking(int displayId) {
+        // Indicates whether this request is for removing the override.
+        final boolean removingOverride = displayId == Display.INVALID_DISPLAY;
+
         synchronized (mAdditionalDisplayInputPropertiesLock) {
             mOverriddenPointerDisplayId = displayId;
-            if (displayId != Display.INVALID_DISPLAY) {
+            if (!removingOverride) {
                 final AdditionalDisplayInputProperties properties =
                         mAdditionalDisplayInputProperties.get(displayId);
                 if (properties != null) {
@@ -1972,9 +2027,30 @@
                     updatePointerIconVisibleLocked(properties.pointerIconVisible);
                 }
             }
+            if (!updatePointerDisplayId() && mAcknowledgedPointerDisplayId == displayId) {
+                // The requested pointer display is already set.
+                return true;
+            }
+            if (removingOverride && mAcknowledgedPointerDisplayId == Display.INVALID_DISPLAY) {
+                // The pointer display override is being removed, but the current pointer display
+                // is already invalid. This can happen when the PointerController is destroyed as a
+                // result of the removal of all input devices that can control the pointer.
+                return true;
+            }
+            try {
+                // The pointer display changed, so wait until the change has propagated.
+                mAdditionalDisplayInputPropertiesLock.wait(5_000 /*mills*/);
+            } catch (InterruptedException ignored) {
+            }
+            // This request succeeds in two cases:
+            // - This request was to remove the override, in which case the new pointer display
+            //   could be anything that WM has set.
+            // - We are setting a new override, in which case the request only succeeds if the
+            //   reported new displayId is the one we requested. This check ensures that if two
+            //   competing overrides are requested in succession, the caller can be notified if one
+            //   of them fails.
+            return  removingOverride || mAcknowledgedPointerDisplayId == displayId;
         }
-        // TODO(b/215597605): trigger MousePositionTracker update
-        mNative.notifyPointerDisplayIdChanged();
     }
 
     private int getVirtualMousePointerDisplayId() {
@@ -3156,18 +3232,6 @@
 
     // Native callback.
     @SuppressWarnings("unused")
-    private int getPointerDisplayId() {
-        synchronized (mAdditionalDisplayInputPropertiesLock) {
-            // Prefer the override to all other displays.
-            if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) {
-                return mOverriddenPointerDisplayId;
-            }
-        }
-        return mWindowManagerCallbacks.getPointerDisplayId();
-    }
-
-    // Native callback.
-    @SuppressWarnings("unused")
     private String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) {
         if (!mSystemReady) {
             return null;
@@ -3206,6 +3270,26 @@
         return null;
     }
 
+    private static class PointerDisplayIdChangedArgs {
+        final int mPointerDisplayId;
+        final float mXPosition;
+        final float mYPosition;
+        PointerDisplayIdChangedArgs(int pointerDisplayId, float xPosition, float yPosition) {
+            mPointerDisplayId = pointerDisplayId;
+            mXPosition = xPosition;
+            mYPosition = yPosition;
+        }
+    }
+
+    // Native callback.
+    @SuppressWarnings("unused")
+    @VisibleForTesting
+    void onPointerDisplayIdChanged(int pointerDisplayId, float xPosition, float yPosition) {
+        mHandler.obtainMessage(MSG_POINTER_DISPLAY_ID_CHANGED,
+                new PointerDisplayIdChangedArgs(pointerDisplayId, xPosition,
+                        yPosition)).sendToTarget();
+    }
+
     /**
      * Callback interface implemented by the Window Manager.
      */
@@ -3329,6 +3413,14 @@
          */
         @Nullable
         SurfaceControl createSurfaceForGestureMonitor(String name, int displayId);
+
+        /**
+         * Notify WindowManagerService when the display of the mouse pointer changes.
+         * @param displayId The display on which the mouse pointer is shown.
+         * @param x The x coordinate of the mouse pointer.
+         * @param y The y coordinate of the mouse pointer.
+         */
+        void notifyPointerDisplayIdChanged(int displayId, float x, float y);
     }
 
     /**
@@ -3381,6 +3473,9 @@
                     boolean inTabletMode = (boolean) args.arg1;
                     deliverTabletModeChanged(whenNanos, inTabletMode);
                     break;
+                case MSG_POINTER_DISPLAY_ID_CHANGED:
+                    handlePointerDisplayIdChanged((PointerDisplayIdChangedArgs) msg.obj);
+                    break;
             }
         }
     }
@@ -3631,8 +3726,9 @@
         }
 
         @Override
-        public void setVirtualMousePointerDisplayId(int pointerDisplayId) {
-            InputManagerService.this.setVirtualMousePointerDisplayId(pointerDisplayId);
+        public boolean setVirtualMousePointerDisplayId(int pointerDisplayId) {
+            return InputManagerService.this
+                    .setVirtualMousePointerDisplayIdBlocking(pointerDisplayId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 2169155..81882d2 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -176,6 +176,9 @@
 
     void cancelCurrentTouch();
 
+    /** Set the displayId on which the mouse cursor should be shown. */
+    void setPointerDisplayId(int displayId);
+
     /** The native implementation of InputManagerService methods. */
     class NativeImpl implements NativeInputManagerService {
         /** Pointer to native input manager service object, used by native code. */
@@ -388,5 +391,8 @@
 
         @Override
         public native void cancelCurrentTouch();
+
+        @Override
+        public native void setPointerDisplayId(int displayId);
     }
 }
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index a09aa7c..b4230c1 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -55,10 +55,8 @@
     private final PermissionManagerServiceInternal mPmi;
     private final IPackageManager mPackageManager;
     private final IPermissionManager mPermManager;
-    // TODO (b/194833441): Remove this boolean (but keep the isMigrationEnabled() method)
-    //  when the migration is enabled
+    // TODO (b/194833441): Remove when the migration is enabled
     private final boolean mMigrationEnabled;
-    private final boolean mIsTv;
     private final boolean mForceUserSetOnUpgrade;
 
     public PermissionHelper(PermissionManagerServiceInternal pmi, IPackageManager packageManager,
@@ -69,17 +67,10 @@
         mPermManager = permManager;
         mMigrationEnabled = migrationEnabled;
         mForceUserSetOnUpgrade = forceUserSetOnUpgrade;
-        boolean isTv;
-        try {
-            isTv = mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK, 0);
-        } catch (RemoteException e) {
-            isTv = false;
-        }
-        mIsTv = isTv;
     }
 
     public boolean isMigrationEnabled() {
-        return mMigrationEnabled && !mIsTv;
+        return mMigrationEnabled;
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index ba4d09f..6400502 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4579,7 +4579,7 @@
                 pw.print(" (override=true)");
             }
             pw.println();
-            if (ps.getPkg().getQueriesPackages().isEmpty()) {
+            if (!ps.getPkg().getQueriesPackages().isEmpty()) {
                 pw.append(prefix).append("  queriesPackages=")
                         .println(ps.getPkg().getQueriesPackages());
             }
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 0834417..3c779f3 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -727,7 +727,8 @@
                 return false;
             }
 
-            if (mKeyguardManager != null && mKeyguardManager.isDeviceLocked(userId)) {
+            if (requiresAuthentication() && mKeyguardManager != null
+                    && mKeyguardManager.isDeviceLocked(userId)) {
                 Log.i(TAG, "Can't change mic/cam toggle while device is locked");
                 return false;
             }
@@ -993,6 +994,13 @@
         }
 
         @Override
+        public boolean requiresAuthentication() {
+            enforceObserveSensorPrivacyPermission();
+            return mContext.getResources()
+                    .getBoolean(R.bool.config_sensorPrivacyRequiresAuthentication);
+        }
+
+        @Override
         public void showSensorUseDialog(int sensor) {
             if (Binder.getCallingUid() != Process.SYSTEM_UID) {
                 throw new SecurityException("Can only be called by the system uid");
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index d374814..4b8c7c1 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -122,16 +122,9 @@
             if (!TrustManagerService.ENABLE_ACTIVE_UNLOCK_FLAG) {
                 return;
             }
-            if (!mWaitingForTrustableDowngrade) {
-                return;
-            }
             // are these the broadcasts we want to listen to
-            if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())
-                    || Intent.ACTION_USER_PRESENT.equals(intent.getAction())) {
-                mTrusted = false;
-                mTrustable = true;
-                mWaitingForTrustableDowngrade = false;
-                mTrustManagerService.updateTrust(mUserId, 0);
+            if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
+                downgradeToTrustable();
             }
         }
     };
@@ -480,8 +473,7 @@
         final String pathUri = mAlarmIntent.toUri(Intent.URI_INTENT_SCHEME);
         alarmFilter.addDataPath(pathUri, PatternMatcher.PATTERN_LITERAL);
 
-        IntentFilter trustableFilter = new IntentFilter(Intent.ACTION_USER_PRESENT);
-        trustableFilter.addAction(Intent.ACTION_SCREEN_OFF);
+        IntentFilter trustableFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
 
         // Schedules a restart for when connecting times out. If the connection succeeds,
         // the restart is canceled in mCallback's onConnected.
@@ -668,6 +660,19 @@
         mTrustable = false;
     }
 
+    /**
+     * Downgrades the trustagent to trustable as a result of a keyguard or screen related event, and
+     * then updates the trust state of the phone to reflect the change.
+     */
+    public void downgradeToTrustable() {
+        if (mWaitingForTrustableDowngrade) {
+            mWaitingForTrustableDowngrade = false;
+            mTrusted = false;
+            mTrustable = true;
+            mTrustManagerService.updateTrust(mUserId, 0);
+        }
+    }
+
     public boolean isManagingTrust() {
         return mManagingTrust && !mTrustDisabledByDpm;
     }
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 8f4ddea..80ce70d 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -1184,6 +1184,22 @@
         return false;
     }
 
+    /**
+     * We downgrade to trustable whenever keyguard changes its showing value.
+     *  - becomes showing: something has caused the device to show keyguard which happens due to
+     *  user intent to lock the device either through direct action or a timeout
+     *  - becomes not showing: keyguard was dismissed and we no longer need to keep the device
+     *  unlocked
+     *  */
+    private void dispatchTrustableDowngrade() {
+        for (int i = 0; i < mActiveAgents.size(); i++) {
+            AgentInfo info = mActiveAgents.valueAt(i);
+            if (info.userId == mCurrentUser) {
+                info.agent.downgradeToTrustable();
+            }
+        }
+    }
+
     private List<String> getTrustGrantedMessages(int userId) {
         if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) {
             return new ArrayList<>();
@@ -1752,6 +1768,7 @@
                     refreshDeviceLockedForUser(UserHandle.USER_ALL);
                     break;
                 case MSG_KEYGUARD_SHOWING_CHANGED:
+                    dispatchTrustableDowngrade();
                     refreshDeviceLockedForUser(mCurrentUser);
                     break;
                 case MSG_START_USER:
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index b97ee7e..b2f27eb 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2732,8 +2732,8 @@
         intentActivity.getTaskFragment().clearLastPausedActivity();
         Task intentTask = intentActivity.getTask();
 
-        // Only update the target-root-task when it is not indicated.
         if (mTargetRootTask == null) {
+            // Update launch target task when it is not indicated.
             if (mSourceRecord != null && mSourceRecord.mLaunchRootTask != null) {
                 // Inherit the target-root-task from source to ensure trampoline activities will be
                 // launched into the same root task.
@@ -2742,6 +2742,17 @@
                 mTargetRootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, intentTask,
                         mOptions);
             }
+        } else {
+            // If a launch target indicated, and the matching task is already in the adjacent task
+            // of the launch target. Adjust to use the adjacent task as its launch target. So the
+            // existing task will be launched into the closer one and won't be reparent redundantly.
+            // TODO(b/231541706): Migrate the logic to wm-shell after having proper APIs to help
+            //  resolve target task without actually starting the activity.
+            final Task adjacentTargetTask = mTargetRootTask.getAdjacentTaskFragment() != null
+                    ? mTargetRootTask.getAdjacentTaskFragment().asTask() : null;
+            if (adjacentTargetTask != null && intentActivity.isDescendantOf(adjacentTargetTask)) {
+                mTargetRootTask = adjacentTargetTask;
+            }
         }
 
         // If the target task is not in the front, then we need to bring it to the front...
@@ -2771,7 +2782,7 @@
                     intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
                 }
 
-                if (mTargetRootTask == intentActivity.getRootTask()) {
+                if (intentActivity.isDescendantOf(mTargetRootTask)) {
                     // TODO(b/151572268): Figure out a better way to move tasks in above 2-levels
                     //  tasks hierarchies.
                     if (mTargetRootTask != intentTask
@@ -2818,19 +2829,6 @@
         mTargetRootTask = intentActivity.getRootTask();
         mSupervisor.handleNonResizableTaskIfNeeded(intentTask, WINDOWING_MODE_UNDEFINED,
                 mRootWindowContainer.getDefaultTaskDisplayArea(), mTargetRootTask);
-
-        // We need to check if there is a launch root task in TDA for this target root task.
-        // If it exist, we need to reparent target root task from TDA to launch root task.
-        final TaskDisplayArea tda = mTargetRootTask.getDisplayArea();
-        final Task launchRootTask = tda.getLaunchRootTask(mTargetRootTask.getWindowingMode(),
-                mTargetRootTask.getActivityType(), null /** options */, mSourceRootTask,
-                mLaunchFlags);
-        // If target root task is created by organizer, let organizer handle reparent itself.
-        if (!mTargetRootTask.mCreatedByOrganizer && launchRootTask != null
-                && launchRootTask != mTargetRootTask) {
-            mTargetRootTask.reparent(launchRootTask, POSITION_TOP);
-            mTargetRootTask = launchRootTask;
-        }
     }
 
     private void resumeTargetRootTaskIfNeeded() {
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 701fc94..cb65597 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -68,6 +68,7 @@
 import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldAttachNavBarToApp;
 import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldStartNonAppWindowAnimationsForKeyguardExit;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.WallpaperAnimationAdapter.shouldStartWallpaperAnimation;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
@@ -1142,13 +1143,17 @@
             if (activity == null) {
                 continue;
             }
+            if (activity.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
+                ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
+                        "Delaying app transition for recents animation to finish");
+                return false;
+            }
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                     "Check opening app=%s: allDrawn=%b startingDisplayed=%b "
                             + "startingMoved=%b isRelaunching()=%b startingWindow=%s",
                     activity, activity.allDrawn, activity.startingDisplayed,
                     activity.startingMoved, activity.isRelaunching(),
                     activity.mStartingWindow);
-
             final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
             if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) {
                 return false;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 2975a95..d0baa23 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2922,7 +2922,7 @@
     }
 
     DisplayCutout loadDisplayCutout(int displayWidth, int displayHeight) {
-        if (mDisplayPolicy == null) {
+        if (mDisplayPolicy == null || mInitialDisplayCutout == null) {
             return null;
         }
         return DisplayCutout.fromResourcesRectApproximation(
@@ -2931,7 +2931,7 @@
     }
 
     RoundedCorners loadRoundedCorners(int displayWidth, int displayHeight) {
-        if (mDisplayPolicy == null) {
+        if (mDisplayPolicy == null || mInitialRoundedCorners == null) {
             return null;
         }
         return RoundedCorners.fromResources(
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 67dd89e..33cdd2e 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -270,6 +270,22 @@
         }
     }
 
+    @Override
+    public void notifyPointerDisplayIdChanged(int displayId, float x, float y) {
+        synchronized (mService.mGlobalLock) {
+            mService.setMousePointerDisplayId(displayId);
+            if (displayId == Display.INVALID_DISPLAY) return;
+
+            final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
+            if (dc == null) {
+                Slog.wtf(TAG, "The mouse pointer was moved to display " + displayId
+                        + " that does not have a valid DisplayContent.");
+                return;
+            }
+            mService.restorePointerIconLocked(dc, x, y);
+        }
+    }
+
     /** Waits until the built-in input devices have been configured. */
     public boolean waitForInputDevicesReady(long timeoutMillis) {
         synchronized (mInputDevicesReadyMonitor) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index c7bc513..a480c37 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1584,6 +1584,14 @@
         return true;
     }
 
+    void forAllWindowContainers(Consumer<WindowContainer> callback) {
+        callback.accept(this);
+        final int count = mChildren.size();
+        for (int i = 0; i < count; i++) {
+            mChildren.get(i).forAllWindowContainers(callback);
+        }
+    }
+
     /**
      * For all windows at or below this container call the callback.
      * @param   callback Calls the {@link ToBooleanFunction#apply} method for each window found and
@@ -3741,8 +3749,16 @@
             }
             // Otherwise this is the "root" of a synced subtree, so continue on to preparation.
         }
+
         // This container's situation has changed so we need to restart its sync.
-        mSyncState = SYNC_STATE_NONE;
+        // We cannot reset the sync without a chance of a deadlock since it will request a new
+        // buffer from the app process. This could cause issues if the app has run out of buffers
+        // since the previous buffer was already synced and is still held in a transaction.
+        // Resetting syncState violates the policies outlined in BlastSyncEngine.md so for now
+        // disable this when shell transitions is disabled.
+        if (mTransitionController.isShellTransitionsEnabled()) {
+            mSyncState = SYNC_STATE_NONE;
+        }
         prepareSync();
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7b77fd0..3906a19 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -118,6 +118,7 @@
 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
 import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
@@ -3050,13 +3051,22 @@
         }
     }
 
+
     void cleanupRecentsAnimation(@RecentsAnimationController.ReorderMode int reorderMode) {
         if (mRecentsAnimationController != null) {
             final RecentsAnimationController controller = mRecentsAnimationController;
             mRecentsAnimationController = null;
             controller.cleanupAnimation(reorderMode);
-            // TODO(mult-display): currently only default display support recents animation.
-            getDefaultDisplayContentLocked().mAppTransition.updateBooster();
+            // TODO(multi-display): currently only default display support recents animation.
+            // Cancel any existing app transition animation running in the legacy transition
+            // framework.
+            final DisplayContent dc = getDefaultDisplayContentLocked();
+            dc.mAppTransition.freeze();
+            dc.forAllWindowContainers((wc) -> {
+                if (wc.isAnimating(TRANSITION, ANIMATION_TYPE_APP_TRANSITION)) {
+                    wc.cancelAnimation();
+                }
+            });
         }
     }
 
@@ -7198,18 +7208,42 @@
         private float mLatestMouseX;
         private float mLatestMouseY;
 
-        void updatePosition(float x, float y) {
+        /**
+         * The display that the pointer (mouse cursor) is currently shown on. This is updated
+         * directly by InputManagerService when the pointer display changes.
+         */
+        private int mPointerDisplayId = INVALID_DISPLAY;
+
+        /**
+         * Update the mouse cursor position as a result of a mouse movement.
+         * @return true if the position was successfully updated, false otherwise.
+         */
+        boolean updatePosition(int displayId, float x, float y) {
             synchronized (this) {
                 mLatestEventWasMouse = true;
+
+                if (displayId != mPointerDisplayId) {
+                    // The display of the position update does not match the display on which the
+                    // mouse pointer is shown, so do not update the position.
+                    return false;
+                }
                 mLatestMouseX = x;
                 mLatestMouseY = y;
+                return true;
+            }
+        }
+
+        void setPointerDisplayId(int displayId) {
+            synchronized (this) {
+                mPointerDisplayId = displayId;
             }
         }
 
         @Override
         public void onPointerEvent(MotionEvent motionEvent) {
             if (motionEvent.isFromSource(InputDevice.SOURCE_MOUSE)) {
-                updatePosition(motionEvent.getRawX(), motionEvent.getRawY());
+                updatePosition(motionEvent.getDisplayId(), motionEvent.getRawX(),
+                        motionEvent.getRawY());
             } else {
                 synchronized (this) {
                     mLatestEventWasMouse = false;
@@ -7219,6 +7253,7 @@
     };
 
     void updatePointerIcon(IWindow client) {
+        int pointerDisplayId;
         float mouseX, mouseY;
 
         synchronized(mMousePositionTracker) {
@@ -7227,6 +7262,7 @@
             }
             mouseX = mMousePositionTracker.mLatestMouseX;
             mouseY = mMousePositionTracker.mLatestMouseY;
+            pointerDisplayId = mMousePositionTracker.mPointerDisplayId;
         }
 
         synchronized (mGlobalLock) {
@@ -7243,6 +7279,10 @@
             if (displayContent == null) {
                 return;
             }
+            if (pointerDisplayId != displayContent.getDisplayId()) {
+                // Do not let the pointer icon be updated by a window on a different display.
+                return;
+            }
             WindowState windowUnderPointer =
                     displayContent.getTouchableWinAtPointLocked(mouseX, mouseY);
             if (windowUnderPointer != callingWin) {
@@ -7260,7 +7300,11 @@
 
     void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) {
         // Mouse position tracker has not been getting updates while dragging, update it now.
-        mMousePositionTracker.updatePosition(latestX, latestY);
+        if (!mMousePositionTracker.updatePosition(
+                displayContent.getDisplayId(), latestX, latestY)) {
+            // The mouse position could not be updated, so ignore this request.
+            return;
+        }
 
         WindowState windowUnderPointer =
                 displayContent.getTouchableWinAtPointLocked(latestX, latestY);
@@ -7284,6 +7328,10 @@
         }
     }
 
+    void setMousePointerDisplayId(int displayId) {
+        mMousePositionTracker.setPointerDisplayId(displayId);
+    }
+
     /**
      * Update a tap exclude region in the window identified by the provided id. Touches down on this
      * region will not:
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 49a4021..93152f2 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -66,13 +66,13 @@
 // Defines the maximum amount of VMAs we can send per process_madvise syscall.
 // Currently this is set to UIO_MAXIOV which is the maximum segments allowed by
 // iovec implementation used by process_madvise syscall
-#define MAX_VMAS_PER_BATCH UIO_MAXIOV
+#define MAX_VMAS_PER_COMPACTION UIO_MAXIOV
 
 // Maximum bytes that we can send per process_madvise syscall once this limit
 // is reached we split the remaining VMAs into another syscall. The MAX_RW_COUNT
 // limit is imposed by iovec implementation. However, if you want to use a smaller
-// limit, it has to be a page aligned value.
-#define MAX_BYTES_PER_BATCH MAX_RW_COUNT
+// limit, it has to be a page aligned value, otherwise, compaction would fail.
+#define MAX_BYTES_PER_COMPACTION MAX_RW_COUNT
 
 // Selected a high enough number to avoid clashing with linux errno codes
 #define ERROR_COMPACTION_CANCELLED -1000
@@ -83,181 +83,6 @@
 // before starting next VMA batch
 static std::atomic<bool> cancelRunningCompaction;
 
-// A VmaBatch represents a set of VMAs that can be processed
-// as VMAs are processed by client code it is expected that the
-// VMAs get consumed which means they are discarded as they are
-// processed so that the first element always is the next element
-// to be sent
-struct VmaBatch {
-    struct iovec* vmas;
-    // total amount of VMAs to reach the end of iovec
-    int totalVmas;
-    // total amount of bytes that are remaining within iovec
-    uint64_t totalBytes;
-};
-
-// Advances the iterator by the specified amount of bytes.
-// This is used to remove already processed or no longer
-// needed parts of the batch.
-// Returns total bytes consumed
-int consumeBytes(VmaBatch& batch, uint64_t bytesToConsume) {
-    int index = 0;
-    if (CC_UNLIKELY(bytesToConsume) < 0) {
-        LOG(ERROR) << "Cannot consume negative bytes for VMA batch !";
-        return 0;
-    }
-
-    if (bytesToConsume > batch.totalBytes) {
-        // Avoid consuming more bytes than available
-        bytesToConsume = batch.totalBytes;
-    }
-
-    uint64_t bytesConsumed = 0;
-    while (bytesConsumed < bytesToConsume) {
-        if (CC_UNLIKELY(index >= batch.totalVmas)) {
-            // reach the end of the batch
-            return bytesConsumed;
-        }
-        if (CC_UNLIKELY(bytesConsumed + batch.vmas[index].iov_len > bytesToConsume)) {
-            // this is the whole VMA that will be consumed
-            break;
-        }
-        bytesConsumed += batch.vmas[index].iov_len;
-        batch.totalBytes -= batch.vmas[index].iov_len;
-        --batch.totalVmas;
-        ++index;
-    }
-
-    // Move pointer to consume all the whole VMAs
-    batch.vmas = batch.vmas + index;
-
-    // Consume the rest of the bytes partially at last VMA in batch
-    uint64_t bytesLeftToConsume = bytesToConsume - bytesConsumed;
-    bytesConsumed += bytesLeftToConsume;
-    if (batch.totalVmas > 0) {
-        batch.vmas[0].iov_base = (void*)((uint64_t)batch.vmas[0].iov_base + bytesLeftToConsume);
-    }
-
-    return bytesConsumed;
-}
-
-// given a source of vmas this class will act as a factory
-// of VmaBatch objects and it will allow generating batches
-// until there are no more left in the source vector.
-// Note: the class does not actually modify the given
-// vmas vector, instead it iterates on it until the end.
-class VmaBatchCreator {
-    const std::vector<Vma>* sourceVmas;
-    // This is the destination array where batched VMAs will be stored
-    // it gets encapsulated into a VmaBatch which is the object
-    // meant to be used by client code.
-    struct iovec* destVmas;
-
-    // Parameters to keep track of the iterator on the source vmas
-    int currentIndex_;
-    uint64_t currentOffset_;
-
-public:
-    VmaBatchCreator(const std::vector<Vma>* vmasToBatch, struct iovec* destVmasVec)
-          : sourceVmas(vmasToBatch), destVmas(destVmasVec), currentIndex_(0), currentOffset_(0) {}
-
-    int currentIndex() { return currentIndex_; }
-    uint64_t currentOffset() { return currentOffset_; }
-
-    // Generates a batch and moves the iterator on the source vmas
-    // past the last VMA in the batch.
-    // Returns true on success, false on failure
-    bool createNextBatch(VmaBatch& batch) {
-        if (currentIndex_ >= MAX_VMAS_PER_BATCH && currentIndex_ >= sourceVmas->size()) {
-            return false;
-        }
-
-        const std::vector<Vma>& vmas = *sourceVmas;
-        batch.vmas = destVmas;
-        uint64_t totalBytesInBatch = 0;
-        int indexInBatch = 0;
-
-        // Add VMAs to the batch up until we consumed all the VMAs or
-        // reached any imposed limit of VMAs per batch.
-        while (indexInBatch < MAX_VMAS_PER_BATCH && currentIndex_ < vmas.size()) {
-            uint64_t vmaStart = vmas[currentIndex_].start + currentOffset_;
-            uint64_t vmaSize = vmas[currentIndex_].end - vmaStart;
-            if (CC_UNLIKELY(vmaSize == 0)) {
-                // No more bytes to batch for this VMA, move to next one
-                // this only happens if a batch partially consumed bytes
-                // and offset landed at exactly the end of a vma
-                continue;
-            }
-            batch.vmas[indexInBatch].iov_base = (void*)vmaStart;
-            uint64_t bytesAvailableInBatch = MAX_BYTES_PER_BATCH - totalBytesInBatch;
-
-            if (vmaSize >= bytesAvailableInBatch) {
-                // VMA would exceed the max available bytes in batch
-                // clamp with available bytes and finish batch.
-                vmaSize = bytesAvailableInBatch;
-                currentOffset_ += bytesAvailableInBatch;
-            }
-
-            batch.vmas[indexInBatch].iov_len = vmaSize;
-            totalBytesInBatch += vmaSize;
-
-            ++indexInBatch;
-            if (totalBytesInBatch >= MAX_BYTES_PER_BATCH) {
-                // Reached max bytes quota so this marks
-                // the end of the batch
-                break;
-            }
-
-            // Fully finished current VMA, move to next one
-            currentOffset_ = 0;
-            ++currentIndex_;
-        }
-        // Vmas where fully filled and we are past the last filled index.
-        batch.totalVmas = indexInBatch;
-        batch.totalBytes = totalBytesInBatch;
-        return true;
-    }
-};
-
-// Madvise a set of VMAs given in a batch for a specific process
-// The total number of bytes successfully madvised will be set on
-// outBytesProcessed.
-// Returns 0 on success and standard linux -errno code returned by
-// process_madvise on failure
-int madviseVmasFromBatch(unique_fd& pidfd, VmaBatch& batch, int madviseType,
-                         uint64_t* outBytesProcessed) {
-    if (batch.totalVmas == 0) {
-        // No VMAs in Batch, skip.
-        *outBytesProcessed = 0;
-        return 0;
-    }
-
-    ATRACE_BEGIN(StringPrintf("Madvise %d: %d VMAs", madviseType, batch.totalVmas).c_str());
-    uint64_t bytesProcessedInSend =
-            process_madvise(pidfd, batch.vmas, batch.totalVmas, madviseType, 0);
-    ATRACE_END();
-
-    if (CC_UNLIKELY(bytesProcessedInSend == -1)) {
-        bytesProcessedInSend = 0;
-        if (errno != EINVAL) {
-            // Forward irrecoverable errors and bail out compaction
-            *outBytesProcessed = 0;
-            return -errno;
-        }
-    }
-
-    if (bytesProcessedInSend < batch.totalBytes) {
-        // Did not process all the bytes requested
-        // skip last page which likely failed
-        bytesProcessedInSend += PAGE_SIZE;
-    }
-
-    bytesProcessedInSend = consumeBytes(batch, bytesProcessedInSend);
-
-    *outBytesProcessed = bytesProcessedInSend;
-    return 0;
-}
-
 // Legacy method for compacting processes, any new code should
 // use compactProcess instead.
 static inline void compactProcessProcfs(int pid, const std::string& compactionType) {
@@ -271,6 +96,8 @@
 // If any VMA fails compaction due to -EINVAL it will be skipped and continue.
 // However, if it fails for any other reason, it will bail out and forward the error
 static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseType) {
+    static struct iovec vmasToKernel[MAX_VMAS_PER_COMPACTION];
+
     if (vmas.empty()) {
         return 0;
     }
@@ -281,16 +108,13 @@
         return -errno;
     }
 
-    struct iovec destVmas[MAX_VMAS_PER_BATCH];
-
-    VmaBatch batch;
-    VmaBatchCreator batcher(&vmas, destVmas);
-
     int64_t totalBytesProcessed = 0;
-    while (batcher.createNextBatch(batch)) {
-        uint64_t bytesProcessedInSend;
 
-        do {
+    int64_t vmaOffset = 0;
+    for (int iVma = 0; iVma < vmas.size();) {
+        uint64_t bytesSentToCompact = 0;
+        int iVec = 0;
+        while (iVec < MAX_VMAS_PER_COMPACTION && iVma < vmas.size()) {
             if (CC_UNLIKELY(cancelRunningCompaction.load())) {
                 // There could be a significant delay between when a compaction
                 // is requested and when it is handled during this time our
@@ -300,13 +124,50 @@
                                          StringPrintf("Cancelled compaction for %d", pid).c_str());
                 return ERROR_COMPACTION_CANCELLED;
             }
-            int error = madviseVmasFromBatch(pidfd, batch, madviseType, &bytesProcessedInSend);
-            if (error < 0) {
-                // Returns standard linux errno code
-                return error;
+
+            uint64_t vmaStart = vmas[iVma].start + vmaOffset;
+            uint64_t vmaSize = vmas[iVma].end - vmaStart;
+            if (vmaSize == 0) {
+                goto next_vma;
             }
-            totalBytesProcessed += bytesProcessedInSend;
-        } while (batch.totalBytes > 0);
+            vmasToKernel[iVec].iov_base = (void*)vmaStart;
+            if (vmaSize > MAX_BYTES_PER_COMPACTION - bytesSentToCompact) {
+                // Exceeded the max bytes that could be sent, so clamp
+                // the end to avoid exceeding limit and issue compaction
+                vmaSize = MAX_BYTES_PER_COMPACTION - bytesSentToCompact;
+            }
+
+            vmasToKernel[iVec].iov_len = vmaSize;
+            bytesSentToCompact += vmaSize;
+            ++iVec;
+            if (bytesSentToCompact >= MAX_BYTES_PER_COMPACTION) {
+                // Ran out of bytes within iovec, dispatch compaction.
+                vmaOffset += vmaSize;
+                break;
+            }
+
+        next_vma:
+            // Finished current VMA, and have more bytes remaining
+            vmaOffset = 0;
+            ++iVma;
+        }
+
+        ATRACE_BEGIN(StringPrintf("Compact %d VMAs", iVec).c_str());
+        auto bytesProcessed = process_madvise(pidfd, vmasToKernel, iVec, madviseType, 0);
+        ATRACE_END();
+
+        if (CC_UNLIKELY(bytesProcessed == -1)) {
+            if (errno == EINVAL) {
+                // This error is somewhat common due to an unevictable VMA if this is
+                // the case silently skip the bad VMA and continue compacting the rest.
+                continue;
+            } else {
+                // Forward irrecoverable errors and bail out compaction
+                return -errno;
+            }
+        }
+
+        totalBytesProcessed += bytesProcessed;
     }
 
     return totalBytesProcessed;
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 3c5ebe7..32adac7 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -107,6 +107,7 @@
     jmethodID interceptKeyBeforeDispatching;
     jmethodID dispatchUnhandledKey;
     jmethodID checkInjectEventsPermission;
+    jmethodID onPointerDisplayIdChanged;
     jmethodID onPointerDownOutsideFocus;
     jmethodID getVirtualKeyQuietTimeMillis;
     jmethodID getExcludedDeviceNames;
@@ -120,7 +121,6 @@
     jmethodID getLongPressTimeout;
     jmethodID getPointerLayer;
     jmethodID getPointerIcon;
-    jmethodID getPointerDisplayId;
     jmethodID getKeyboardLayoutOverlay;
     jmethodID getDeviceAlias;
     jmethodID getTouchCalibrationForInputDevice;
@@ -277,6 +277,7 @@
     void setFocusedDisplay(int32_t displayId);
     void setInputDispatchMode(bool enabled, bool frozen);
     void setSystemUiLightsOut(bool lightsOut);
+    void setPointerDisplayId(int32_t displayId);
     void setPointerSpeed(int32_t speed);
     void setPointerAcceleration(float acceleration);
     void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
@@ -288,7 +289,6 @@
     void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled);
     void setCustomPointerIcon(const SpriteIcon& icon);
     void setMotionClassifierEnabled(bool enabled);
-    void notifyPointerDisplayIdChanged();
 
     /* --- InputReaderPolicyInterface implementation --- */
 
@@ -346,6 +346,7 @@
             std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId);
     virtual int32_t getDefaultPointerIconId();
     virtual int32_t getCustomPointerIconId();
+    virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos);
 
 private:
     sp<InputManagerInterface> mInputManager;
@@ -394,7 +395,6 @@
     void updateInactivityTimeoutLocked();
     void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
     void ensureSpriteControllerLocked();
-    int32_t getPointerDisplayId();
     sp<SurfaceControl> getParentSurfaceForPointers(int displayId);
     static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
 
@@ -498,13 +498,9 @@
         }
     }
 
-    // Get the preferred pointer controller displayId.
-    int32_t pointerDisplayId = getPointerDisplayId();
-
     { // acquire lock
         AutoMutex _l(mLock);
         mLocked.viewports = viewports;
-        mLocked.pointerDisplayId = pointerDisplayId;
         std::shared_ptr<PointerController> controller = mLocked.pointerController.lock();
         if (controller != nullptr) {
             controller->onDisplayViewportsUpdated(mLocked.viewports);
@@ -666,15 +662,12 @@
     return controller;
 }
 
-int32_t NativeInputManager::getPointerDisplayId() {
+void NativeInputManager::onPointerDisplayIdChanged(int32_t pointerDisplayId, float xPos,
+                                                   float yPos) {
     JNIEnv* env = jniEnv();
-    jint pointerDisplayId = env->CallIntMethod(mServiceObj,
-            gServiceClassInfo.getPointerDisplayId);
-    if (checkAndClearExceptionFromCallback(env, "getPointerDisplayId")) {
-        pointerDisplayId = ADISPLAY_ID_DEFAULT;
-    }
-
-    return pointerDisplayId;
+    env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDisplayIdChanged, pointerDisplayId,
+                        xPos, yPos);
+    checkAndClearExceptionFromCallback(env, "onPointerDisplayIdChanged");
 }
 
 sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(int displayId) {
@@ -1032,6 +1025,22 @@
                                                                : InactivityTimeout::NORMAL);
 }
 
+void NativeInputManager::setPointerDisplayId(int32_t displayId) {
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        if (mLocked.pointerDisplayId == displayId) {
+            return;
+        }
+
+        ALOGI("Setting pointer display id to %d.", displayId);
+        mLocked.pointerDisplayId = displayId;
+    } // release lock
+
+    mInputManager->getReader().requestRefreshConfiguration(
+            InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+}
+
 void NativeInputManager::setPointerSpeed(int32_t speed) {
     { // acquire lock
         AutoMutex _l(mLock);
@@ -1494,18 +1503,6 @@
     mInputManager->getClassifier().setMotionClassifierEnabled(enabled);
 }
 
-void NativeInputManager::notifyPointerDisplayIdChanged() {
-    int32_t pointerDisplayId = getPointerDisplayId();
-
-    { // acquire lock
-        AutoMutex _l(mLock);
-        mLocked.pointerDisplayId = pointerDisplayId;
-    } // release lock
-
-    mInputManager->getReader().requestRefreshConfiguration(
-            InputReaderConfiguration::CHANGE_DISPLAY_INFO);
-}
-
 // ----------------------------------------------------------------------------
 
 static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) {
@@ -2199,11 +2196,6 @@
             InputReaderConfiguration::CHANGE_DISPLAY_INFO);
 }
 
-static void nativeNotifyPointerDisplayIdChanged(JNIEnv* env, jobject nativeImplObj) {
-    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
-    im->notifyPointerDisplayIdChanged();
-}
-
 static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jobject nativeImplObj,
                                                          jint displayId, jboolean isEligible) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2321,6 +2313,11 @@
     im->getInputManager()->getDispatcher().cancelCurrentTouch();
 }
 
+static void nativeSetPointerDisplayId(JNIEnv* env, jobject nativeImplObj, jint displayId) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+    im->setPointerDisplayId(displayId);
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gInputManagerMethods[] = {
@@ -2393,7 +2390,6 @@
         {"canDispatchToDisplay", "(II)Z", (void*)nativeCanDispatchToDisplay},
         {"notifyPortAssociationsChanged", "()V", (void*)nativeNotifyPortAssociationsChanged},
         {"changeUniqueIdAssociation", "()V", (void*)nativeChangeUniqueIdAssociation},
-        {"notifyPointerDisplayIdChanged", "()V", (void*)nativeNotifyPointerDisplayIdChanged},
         {"setDisplayEligibilityForPointerCapture", "(IZ)V",
          (void*)nativeSetDisplayEligibilityForPointerCapture},
         {"setMotionClassifierEnabled", "(Z)V", (void*)nativeSetMotionClassifierEnabled},
@@ -2403,6 +2399,7 @@
         {"disableSensor", "(II)V", (void*)nativeDisableSensor},
         {"flushSensor", "(II)Z", (void*)nativeFlushSensor},
         {"cancelCurrentTouch", "()V", (void*)nativeCancelCurrentTouch},
+        {"setPointerDisplayId", "(I)V", (void*)nativeSetPointerDisplayId},
 };
 
 #define FIND_CLASS(var, className) \
@@ -2498,6 +2495,9 @@
     GET_METHOD_ID(gServiceClassInfo.checkInjectEventsPermission, clazz,
                   "checkInjectEventsPermission", "(II)Z");
 
+    GET_METHOD_ID(gServiceClassInfo.onPointerDisplayIdChanged, clazz, "onPointerDisplayIdChanged",
+                  "(IFF)V");
+
     GET_METHOD_ID(gServiceClassInfo.onPointerDownOutsideFocus, clazz,
             "onPointerDownOutsideFocus", "(Landroid/os/IBinder;)V");
 
@@ -2537,9 +2537,6 @@
     GET_METHOD_ID(gServiceClassInfo.getPointerIcon, clazz,
             "getPointerIcon", "(I)Landroid/view/PointerIcon;");
 
-    GET_METHOD_ID(gServiceClassInfo.getPointerDisplayId, clazz,
-            "getPointerDisplayId", "()I");
-
     GET_METHOD_ID(gServiceClassInfo.getKeyboardLayoutOverlay, clazz,
             "getKeyboardLayoutOverlay",
             "(Landroid/hardware/input/InputDeviceIdentifier;)[Ljava/lang/String;");
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 8aa9f60..994a767 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -238,7 +238,7 @@
             }
         }
 
-        // called from Device.close()
+        // called from Device.closeLocked()
         public void removeDeviceConnection(DeviceConnection connection) {
             mDeviceConnections.remove(connection.getToken());
             if (mListeners.size() == 0 && mDeviceConnections.size() == 0) {
@@ -294,12 +294,6 @@
             }
 
             for (DeviceConnection connection : mDeviceConnections.values()) {
-                if (connection.getDevice().getDeviceInfo().getType()
-                        == MidiDeviceInfo.TYPE_USB) {
-                    synchronized (mUsbMidiLock) {
-                        removeUsbMidiDeviceLocked(connection.getDevice().getDeviceInfo());
-                    }
-                }
                 connection.getDevice().removeDeviceConnection(connection);
             }
         }
@@ -541,6 +535,13 @@
                 synchronized (mDeviceConnections) {
                     mDeviceConnections.remove(connection);
 
+                    if (connection.getDevice().getDeviceInfo().getType()
+                            == MidiDeviceInfo.TYPE_USB) {
+                        synchronized (mUsbMidiLock) {
+                            removeUsbMidiDeviceLocked(connection.getDevice().getDeviceInfo());
+                        }
+                    }
+
                     if (mDeviceConnections.size() == 0 && mServiceConnection != null) {
                         mContext.unbindService(mServiceConnection);
                         mServiceConnection = null;
@@ -559,6 +560,12 @@
         public void closeLocked() {
             synchronized (mDeviceConnections) {
                 for (DeviceConnection connection : mDeviceConnections) {
+                    if (connection.getDevice().getDeviceInfo().getType()
+                            == MidiDeviceInfo.TYPE_USB) {
+                        synchronized (mUsbMidiLock) {
+                            removeUsbMidiDeviceLocked(connection.getDevice().getDeviceInfo());
+                        }
+                    }
                     connection.getClient().removeDeviceConnection(connection);
                 }
                 mDeviceConnections.clear();
@@ -1401,6 +1408,8 @@
         String deviceName = extractUsbDeviceName(name);
         String tagName = extractUsbDeviceTag(name);
 
+        Log.i(TAG, "Checking " + deviceName + " " + tagName);
+
         // Only one MIDI 2.0 device can be used at once.
         // Multiple MIDI 1.0 devices can be used at once.
         if (mUsbMidiUniversalDeviceInUse.contains(deviceName)
@@ -1420,6 +1429,8 @@
         String deviceName = extractUsbDeviceName(name);
         String tagName = extractUsbDeviceTag(name);
 
+        Log.i(TAG, "Adding " + deviceName + " " + tagName);
+
         if ((tagName).equals(MIDI_UNIVERSAL_STRING)) {
             mUsbMidiUniversalDeviceInUse.add(deviceName);
         } else if ((tagName).equals(MIDI_LEGACY_STRING)) {
@@ -1437,6 +1448,8 @@
         String deviceName = extractUsbDeviceName(name);
         String tagName = extractUsbDeviceTag(name);
 
+        Log.i(TAG, "Removing " + deviceName + " " + tagName);
+
         if ((tagName).equals(MIDI_UNIVERSAL_STRING)) {
             mUsbMidiUniversalDeviceInUse.remove(deviceName);
         } else if ((tagName).equals(MIDI_LEGACY_STRING)) {
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index b4bb04d..77cbb3a 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -19,22 +19,22 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.IInputManager;
-import android.hardware.input.InputManager;
 import android.hardware.input.InputManagerInternal;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.view.Display;
 import android.view.DisplayInfo;
 
-import androidx.test.runner.AndroidJUnit4;
-
 import com.android.server.LocalServices;
 
 import org.junit.Before;
@@ -44,7 +44,8 @@
 import org.mockito.MockitoAnnotations;
 
 @Presubmit
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class InputControllerTest {
 
     @Mock
@@ -56,13 +57,16 @@
     @Mock
     private IInputManager mIInputManagerMock;
 
+    private InputManagerMockHelper mInputManagerMockHelper;
     private InputController mInputController;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        mInputManagerMockHelper = new InputManagerMockHelper(
+                TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock);
 
-        doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
+        doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
         LocalServices.removeServiceForTest(InputManagerInternal.class);
         LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
 
@@ -72,10 +76,10 @@
         LocalServices.removeServiceForTest(DisplayManagerInternal.class);
         LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
 
-        InputManager.resetInstance(mIInputManagerMock);
-        doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
-        doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString());
-        mInputController = new InputController(new Object(), mNativeWrapperMock);
+        // Allow virtual devices to be created on the looper thread for testing.
+        final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true;
+        mInputController = new InputController(new Object(), mNativeWrapperMock,
+                new Handler(TestableLooper.get(this).getLooper()), threadVerifier);
     }
 
     @Test
@@ -83,6 +87,7 @@
         final IBinder deviceToken = new Binder();
         mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
                 /* displayId= */ 1);
+        verify(mNativeWrapperMock).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
         verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
         doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
         mInputController.unregisterInputDevice(deviceToken);
@@ -95,10 +100,12 @@
         final IBinder deviceToken = new Binder();
         mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
                 /* displayId= */ 1);
+        verify(mNativeWrapperMock).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
         verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
         final IBinder deviceToken2 = new Binder();
         mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken2,
                 /* displayId= */ 2);
+        verify(mNativeWrapperMock, times(2)).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
         verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(2));
         mInputController.unregisterInputDevice(deviceToken);
         verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
new file mode 100644
index 0000000..aa2d97e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
@@ -0,0 +1,101 @@
+/*
+ * 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.server.companion.virtual;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.when;
+
+import android.hardware.input.IInputDevicesChangedListener;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManager;
+import android.os.RemoteException;
+import android.testing.TestableLooper;
+import android.view.InputDevice;
+
+import org.mockito.invocation.InvocationOnMock;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.IntStream;
+
+/**
+ * A test utility class used to share the logic for setting up {@link InputManager}'s callback for
+ * when a virtual input device being added.
+ */
+class InputManagerMockHelper {
+    private final TestableLooper mTestableLooper;
+    private final InputController.NativeWrapper mNativeWrapperMock;
+    private final IInputManager mIInputManagerMock;
+    private final List<InputDevice> mDevices = new ArrayList<>();
+    private IInputDevicesChangedListener mDevicesChangedListener;
+
+    InputManagerMockHelper(TestableLooper testableLooper,
+            InputController.NativeWrapper nativeWrapperMock, IInputManager iInputManagerMock)
+            throws Exception {
+        mTestableLooper = testableLooper;
+        mNativeWrapperMock = nativeWrapperMock;
+        mIInputManagerMock = iInputManagerMock;
+
+        doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputMouse(
+                anyString(), anyInt(), anyInt(), anyString());
+        doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputKeyboard(
+                anyString(), anyInt(), anyInt(), anyString());
+        doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputTouchscreen(
+                anyString(), anyInt(), anyInt(), anyString(), anyInt(), anyInt());
+
+        doAnswer(inv -> {
+            mDevicesChangedListener = inv.getArgument(0);
+            return null;
+        }).when(mIInputManagerMock).registerInputDevicesChangedListener(notNull());
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
+        doAnswer(inv -> mDevices.get(inv.getArgument(0)))
+                .when(mIInputManagerMock).getInputDevice(anyInt());
+        doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
+        doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString());
+
+        // Set a new instance of InputManager for testing that uses the IInputManager mock as the
+        // interface to the server.
+        InputManager.resetInstance(mIInputManagerMock);
+    }
+
+    private Void handleNativeOpenInputDevice(InvocationOnMock inv) {
+        Objects.requireNonNull(mDevicesChangedListener,
+                "InputController did not register an InputDevicesChangedListener.");
+        // We only use a subset of the fields of InputDevice in InputController.
+        final InputDevice device = new InputDevice(mDevices.size() /*id*/, 1 /*generation*/, 0,
+                inv.getArgument(0) /*name*/, inv.getArgument(1) /*vendorId*/,
+                inv.getArgument(2) /*productId*/, inv.getArgument(3) /*descriptor*/,
+                true /*isExternal*/, 0 /*sources*/, 0 /*keyboardType*/,
+                null /*keyCharacterMap*/, false /*hasVibrator*/, false /*hasMic*/,
+                false /*hasButtonUnderPad*/, false /*hasSensor*/, false /*hasBattery*/);
+        mDevices.add(device);
+        try {
+            mDevicesChangedListener.onInputDevicesChanged(
+                    mDevices.stream().flatMapToInt(
+                            d -> IntStream.of(d.getId(), d.getGeneration())).toArray());
+        } catch (RemoteException ignored) {
+        }
+        // Process the device added notification.
+        mTestableLooper.processAllMessages();
+        return null;
+    }
+}
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 808f8c2c..cbb9fd7 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
@@ -54,6 +54,7 @@
 import android.content.pm.ApplicationInfo;
 import android.graphics.Point;
 import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.IInputManager;
 import android.hardware.input.InputManagerInternal;
 import android.hardware.input.VirtualKeyEvent;
 import android.hardware.input.VirtualMouseButtonEvent;
@@ -118,6 +119,7 @@
     private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
 
     private Context mContext;
+    private InputManagerMockHelper mInputManagerMockHelper;
     private VirtualDeviceImpl mDeviceImpl;
     private InputController mInputController;
     private AssociationInfo mAssociationInfo;
@@ -146,6 +148,8 @@
     private IAudioConfigChangedCallback mConfigChangedCallback;
     @Mock
     private ApplicationInfo mApplicationInfoMock;
+    @Mock
+    IInputManager mIInputManagerMock;
 
     private ArraySet<ComponentName> getBlockedActivities() {
         ArraySet<ComponentName> blockedActivities = new ArraySet<>();
@@ -170,13 +174,13 @@
     }
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
         LocalServices.removeServiceForTest(DisplayManagerInternal.class);
         LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
 
-        doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
+        doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
         doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt());
         doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt());
         LocalServices.removeServiceForTest(InputManagerInternal.class);
@@ -199,7 +203,13 @@
                 new Handler(TestableLooper.get(this).getLooper()));
         when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
 
-        mInputController = new InputController(new Object(), mNativeWrapperMock);
+        mInputManagerMockHelper = new InputManagerMockHelper(
+                TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock);
+        // Allow virtual devices to be created on the looper thread for testing.
+        final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true;
+        mInputController = new InputController(new Object(), mNativeWrapperMock,
+                new Handler(TestableLooper.get(this).getLooper()), threadVerifier);
+
         mAssociationInfo = new AssociationInfo(1, 0, null,
                 MacAddress.BROADCAST_ADDRESS, "", null, true, false, 0, 0);
 
diff --git a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
new file mode 100644
index 0000000..cb97c9b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
@@ -0,0 +1,222 @@
+/*
+ * 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.server.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.hardware.display.DisplayViewport
+import android.hardware.input.InputManagerInternal
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.view.Display
+import androidx.test.InstrumentationRegistry
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+/**
+ * Tests for {@link InputManagerService}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:InputManagerServiceTests
+ */
+@Presubmit
+class InputManagerServiceTests {
+
+    @get:Rule
+    val rule = MockitoJUnit.rule()!!
+
+    @Mock
+    private lateinit var native: NativeInputManagerService
+
+    @Mock
+    private lateinit var wmCallbacks: InputManagerService.WindowManagerCallbacks
+
+    private lateinit var service: InputManagerService
+    private lateinit var localService: InputManagerInternal
+    private lateinit var context: Context
+    private lateinit var testLooper: TestLooper
+
+    @Before
+    fun setup() {
+        context = spy(ContextWrapper(InstrumentationRegistry.getContext()))
+        testLooper = TestLooper()
+        service =
+            InputManagerService(object : InputManagerService.Injector(context, testLooper.looper) {
+                override fun getNativeService(
+                    service: InputManagerService?
+                ): NativeInputManagerService {
+                    return native
+                }
+
+                override fun registerLocalService(service: InputManagerInternal?) {
+                    localService = service!!
+                }
+            })
+        assertTrue("Local service must be registered", this::localService.isInitialized)
+        service.setWindowManagerCallbacks(wmCallbacks)
+    }
+
+    @Test
+    fun testPointerDisplayUpdatesWhenDisplayViewportsChanged() {
+        val displayId = 123
+        `when`(wmCallbacks.pointerDisplayId).thenReturn(displayId)
+        val viewports = listOf<DisplayViewport>()
+        localService.setDisplayViewports(viewports)
+        verify(native).setDisplayViewports(any(Array<DisplayViewport>::class.java))
+        verify(native).setPointerDisplayId(displayId)
+
+        val x = 42f
+        val y = 314f
+        service.onPointerDisplayIdChanged(displayId, x, y)
+        testLooper.dispatchNext()
+        verify(wmCallbacks).notifyPointerDisplayIdChanged(displayId, x, y)
+    }
+
+    @Test
+    fun testSetVirtualMousePointerDisplayId() {
+        // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
+        // until the native callback happens.
+        var countDownLatch = CountDownLatch(1)
+        val overrideDisplayId = 123
+        Thread {
+            assertTrue("Setting virtual pointer display should succeed",
+                localService.setVirtualMousePointerDisplayId(overrideDisplayId))
+            countDownLatch.countDown()
+        }.start()
+        assertFalse("Setting virtual pointer display should block",
+            countDownLatch.await(100, TimeUnit.MILLISECONDS))
+
+        val x = 42f
+        val y = 314f
+        service.onPointerDisplayIdChanged(overrideDisplayId, x, y)
+        testLooper.dispatchNext()
+        verify(wmCallbacks).notifyPointerDisplayIdChanged(overrideDisplayId, x, y)
+        assertTrue("Native callback unblocks calling thread",
+            countDownLatch.await(100, TimeUnit.MILLISECONDS))
+        verify(native).setPointerDisplayId(overrideDisplayId)
+
+        // Ensure that setting the same override again succeeds immediately.
+        assertTrue("Setting the same virtual mouse pointer displayId again should succeed",
+            localService.setVirtualMousePointerDisplayId(overrideDisplayId))
+
+        // Ensure that we did not query WM for the pointerDisplayId when setting the override
+        verify(wmCallbacks, never()).pointerDisplayId
+
+        // Unset the virtual mouse pointer displayId, and ensure that we query WM for the new
+        // pointer displayId and the calling thread is blocked until the native callback happens.
+        countDownLatch = CountDownLatch(1)
+        val pointerDisplayId = 42
+        `when`(wmCallbacks.pointerDisplayId).thenReturn(pointerDisplayId)
+        Thread {
+            assertTrue("Unsetting virtual mouse pointer displayId should succeed",
+                localService.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY))
+            countDownLatch.countDown()
+        }.start()
+        assertFalse("Unsetting virtual mouse pointer displayId should block",
+            countDownLatch.await(100, TimeUnit.MILLISECONDS))
+
+        service.onPointerDisplayIdChanged(pointerDisplayId, x, y)
+        testLooper.dispatchNext()
+        verify(wmCallbacks).notifyPointerDisplayIdChanged(pointerDisplayId, x, y)
+        assertTrue("Native callback unblocks calling thread",
+            countDownLatch.await(100, TimeUnit.MILLISECONDS))
+        verify(native).setPointerDisplayId(pointerDisplayId)
+    }
+
+    @Test
+    fun testSetVirtualMousePointerDisplayId_unsuccessfulUpdate() {
+        // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
+        // until the native callback happens.
+        val countDownLatch = CountDownLatch(1)
+        val overrideDisplayId = 123
+        Thread {
+            assertFalse("Setting virtual pointer display should be unsuccessful",
+                localService.setVirtualMousePointerDisplayId(overrideDisplayId))
+            countDownLatch.countDown()
+        }.start()
+        assertFalse("Setting virtual pointer display should block",
+            countDownLatch.await(100, TimeUnit.MILLISECONDS))
+
+        val x = 42f
+        val y = 314f
+        // Assume the native callback updates the pointerDisplayId to the incorrect value.
+        service.onPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y)
+        testLooper.dispatchNext()
+        verify(wmCallbacks).notifyPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y)
+        assertTrue("Native callback unblocks calling thread",
+            countDownLatch.await(100, TimeUnit.MILLISECONDS))
+        verify(native).setPointerDisplayId(overrideDisplayId)
+    }
+
+    @Test
+    fun testSetVirtualMousePointerDisplayId_competingRequests() {
+        val firstRequestSyncLatch = CountDownLatch(1)
+        doAnswer {
+            firstRequestSyncLatch.countDown()
+        }.`when`(native).setPointerDisplayId(anyInt())
+
+        val firstRequestLatch = CountDownLatch(1)
+        val firstOverride = 123
+        Thread {
+            assertFalse("Setting virtual pointer display from thread 1 should be unsuccessful",
+                localService.setVirtualMousePointerDisplayId(firstOverride))
+            firstRequestLatch.countDown()
+        }.start()
+        assertFalse("Setting virtual pointer display should block",
+            firstRequestLatch.await(100, TimeUnit.MILLISECONDS))
+
+        assertTrue("Wait for first thread's request should succeed",
+            firstRequestSyncLatch.await(100, TimeUnit.MILLISECONDS))
+
+        val secondRequestLatch = CountDownLatch(1)
+        val secondOverride = 42
+        Thread {
+            assertTrue("Setting virtual mouse pointer from thread 2 should be successful",
+                localService.setVirtualMousePointerDisplayId(secondOverride))
+            secondRequestLatch.countDown()
+        }.start()
+        assertFalse("Setting virtual mouse pointer should block",
+            secondRequestLatch.await(100, TimeUnit.MILLISECONDS))
+
+        val x = 42f
+        val y = 314f
+        // Assume the native callback updates directly to the second request.
+        service.onPointerDisplayIdChanged(secondOverride, x, y)
+        testLooper.dispatchNext()
+        verify(wmCallbacks).notifyPointerDisplayIdChanged(secondOverride, x, y)
+        assertTrue("Native callback unblocks first thread",
+            firstRequestLatch.await(100, TimeUnit.MILLISECONDS))
+        assertTrue("Native callback unblocks second thread",
+            secondRequestLatch.await(100, TimeUnit.MILLISECONDS))
+        verify(native, times(2)).setPointerDisplayId(anyInt())
+    }
+}
\ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index f59ec42..b943275 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -785,6 +785,34 @@
     }
 
     /**
+     * This test ensures that {@link ActivityStarter#setTargetRootTaskIfNeeded} will task the
+     * adjacent task of indicated launch target into account. So the existing task will be launched
+     * into closer target.
+     */
+    @Test
+    public void testAdjustLaunchTargetWithAdjacentTask() {
+        // Create adjacent tasks and put one activity under it
+        final Task parent = new TaskBuilder(mSupervisor).build();
+        final Task adjacentParent = new TaskBuilder(mSupervisor).build();
+        parent.setAdjacentTaskFragment(adjacentParent, true);
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setParentTask(parent)
+                .setCreateTask(true).build();
+
+        // Launch the activity to its adjacent parent
+        final ActivityOptions options = ActivityOptions.makeBasic()
+                .setLaunchRootTask(adjacentParent.mRemoteToken.toWindowContainerToken());
+        prepareStarter(FLAG_ACTIVITY_NEW_TASK, false /* mockGetRootTask */)
+                .setReason("testAdjustLaunchTargetWithAdjacentTask")
+                .setIntent(activity.intent)
+                .setActivityOptions(options.toBundle())
+                .execute();
+
+        // Verify the activity will be launched into the original parent
+        assertTrue(activity.isDescendantOf(parent));
+    }
+
+    /**
      * This test ensures that {@link ActivityStarter#setTargetRootTaskIfNeeded} will
      * move the existing task to front if the current focused root task doesn't have running task.
      */
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index eb6395b..3592158 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -39,8 +39,10 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import static org.junit.Assert.assertEquals;
@@ -48,7 +50,9 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 
 import android.graphics.Rect;
 import android.os.Binder;
@@ -376,7 +380,7 @@
         doReturn(false).when(dc).onDescendantOrientationChanged(any());
         final WindowState exitingAppWindow = createWindow(null /* parent */, TYPE_BASE_APPLICATION,
                 dc, "exiting app");
-        final ActivityRecord exitingActivity= exitingAppWindow.mActivityRecord;
+        final ActivityRecord exitingActivity = exitingAppWindow.mActivityRecord;
         // Wait until everything in animation handler get executed to prevent the exiting window
         // from being removed during WindowSurfacePlacer Traversal.
         waitUntilHandlersIdle();
@@ -405,6 +409,41 @@
     }
 
     @Test
+    public void testDelayWhileRecents() {
+        final DisplayContent dc = createNewDisplay(Display.STATE_ON);
+        doReturn(false).when(dc).onDescendantOrientationChanged(any());
+        final Task task = createTask(dc);
+
+        // Simulate activity1 launches activity2.
+        final ActivityRecord activity1 = createActivityRecord(task);
+        activity1.setVisible(true);
+        activity1.mVisibleRequested = false;
+        activity1.allDrawn = true;
+        final ActivityRecord activity2 = createActivityRecord(task);
+        activity2.setVisible(false);
+        activity2.mVisibleRequested = true;
+        activity2.allDrawn = true;
+
+        dc.mClosingApps.add(activity1);
+        dc.mOpeningApps.add(activity2);
+        dc.prepareAppTransition(TRANSIT_OPEN);
+        assertTrue(dc.mAppTransition.containsTransitRequest(TRANSIT_OPEN));
+
+        // Wait until everything in animation handler get executed to prevent the exiting window
+        // from being removed during WindowSurfacePlacer Traversal.
+        waitUntilHandlersIdle();
+
+        // Start recents
+        doReturn(true).when(task)
+                .isSelfAnimating(anyInt(), eq(ANIMATION_TYPE_RECENTS));
+
+        dc.mAppTransitionController.handleAppTransitionReady();
+
+        verify(activity1, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
+        verify(activity2, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
+    }
+
+    @Test
     public void testGetAnimationStyleResId() {
         // Verify getAnimationStyleResId will return as LayoutParams.windowAnimations when without
         // specifying window type.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
new file mode 100644
index 0000000..16c4c25
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.server.wm.flicker.helpers
+
+import android.app.Instrumentation
+import android.support.test.launcherhelper.ILauncherStrategy
+import android.support.test.launcherhelper.LauncherStrategyFactory
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.parser.toFlickerComponent
+
+class ImeStateInitializeHelper @JvmOverloads constructor(
+    instr: Instrumentation,
+    launcherName: String = ActivityOptions.IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME,
+    component: FlickerComponentName =
+        ActivityOptions.IME_ACTIVITY_INITIALIZE_COMPONENT_NAME.toFlickerComponent(),
+    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
+            .getInstance(instr)
+            .launcherStrategy
+) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
index 9643909..b897ca2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
@@ -28,6 +28,7 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.ImeStateInitializeHelper
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.traces.common.FlickerComponentName
 import org.junit.FixMethodOrder
@@ -71,17 +72,20 @@
 class LaunchAppShowImeOnStartTest(private val testSpec: FlickerTestParameter) {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+    private val initializeApp = ImeStateInitializeHelper(instrumentation)
 
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
             setup {
                 eachRun {
+                    initializeApp.launchViaIntent()
                     this.setRotation(testSpec.startRotation)
                 }
             }
             teardown {
                 eachRun {
+                    initializeApp.exit()
                     testApp.exit()
                 }
             }
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 2842232..43aa4b1 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -52,6 +52,16 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+        <activity android:name=".ImeStateInitializeActivity"
+                  android:theme="@style/no_starting_window"
+                  android:windowSoftInputMode="stateAlwaysHidden"
+                  android:label="ImeStateInitializeActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
         <activity android:name=".SeamlessRotationActivity"
              android:taskAffinity="com.android.server.wm.flicker.testapp.SeamlessRotationActivity"
              android:theme="@style/CutoutShortEdges"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
index 589df38..1d21fd5 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
@@ -42,4 +42,8 @@
         <item name="android:backgroundDimEnabled">false</item>
         <item name="android:windowSoftInputMode">stateUnchanged</item>
     </style>
+
+    <style name="no_starting_window" parent="@android:style/Theme.DeviceDefault">
+        <item name="android:windowDisablePreview">true</item>
+    </style>
 </resources>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index e080709..6cda482 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -37,6 +37,11 @@
             new ComponentName(FLICKER_APP_PACKAGE,
                     FLICKER_APP_PACKAGE + ".ImeActivity");
 
+    public static final String IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME = "ImeStateInitializeActivity";
+    public static final ComponentName IME_ACTIVITY_INITIALIZE_COMPONENT_NAME =
+            new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".ImeStateInitializeActivity");
+
     public static final String SIMPLE_ACTIVITY_LAUNCHER_NAME = "SimpleApp";
     public static final ComponentName SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME =
             new ComponentName(FLICKER_APP_PACKAGE,
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeStateInitializeActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeStateInitializeActivity.java
new file mode 100644
index 0000000..4be79c4
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeStateInitializeActivity.java
@@ -0,0 +1,50 @@
+/*
+ * 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.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A nop {@link Activity} to make sure that the test starts from a deterministic state.
+ *
+ * <p>Currently this {@link Activity} makes sure the following things</p>
+ * <li>
+ *     <ul>Hide the software keyboard with
+ *     {@link android.view.WindowManager.LayoutParams#SOFT_INPUT_STATE_ALWAYS_HIDDEN}</ul>
+ *     <ul>Make sure that the navigation bar (if supported) is rendered with {@link Color#BLACK}.
+ *     </ul>
+ * </li>
+ */
+public class ImeStateInitializeActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        final View view = new View(this);
+        view.setBackgroundColor(Color.WHITE);
+        view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT));
+
+        // Make sure that navigation bar is rendered with black (if supported).
+        getWindow().setNavigationBarColor(Color.BLACK);
+
+        setContentView(view);
+    }
+}
diff --git a/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt b/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt
index 3c6d54d..ae72247 100644
--- a/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt
+++ b/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt
@@ -29,7 +29,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import androidx.test.uiautomator.UiDevice
-import com.google.common.truth.Truth.assertThat
+import android.trust.test.lib.wait
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -74,9 +74,9 @@
         uiDevice.sleep()
         lockStateTrackingRule.assertLocked()
 
+        uiDevice.wakeUp()
         trustAgentRule.agent.grantTrust(
             GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {}
-        uiDevice.wakeUp()
 
         lockStateTrackingRule.assertLocked()
     }
@@ -98,9 +98,9 @@
 
         lockStateTrackingRule.assertLocked()
 
+        uiDevice.wakeUp()
         trustAgentRule.agent.grantTrust(
             GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {}
-        uiDevice.wakeUp()
 
         lockStateTrackingRule.assertUnlocked()
     }
@@ -116,6 +116,7 @@
         uiDevice.sleep()
 
         lockStateTrackingRule.assertLocked()
+        uiDevice.wakeUp()
 
         Log.i(TAG, "Renewing trust and unlocking")
         var result: GrantTrustResult? = null
@@ -124,10 +125,9 @@
             Log.i(TAG, "Callback received; status=${it.status}")
             result = it
         }
-        uiDevice.wakeUp()
         lockStateTrackingRule.assertUnlocked()
 
-        assertThat(result?.status).isEqualTo(STATUS_UNLOCKED_BY_GRANT)
+        wait("callback triggered") { result?.status == STATUS_UNLOCKED_BY_GRANT }
     }
 
     @Test
@@ -141,7 +141,6 @@
         trustAgentRule.agent.revokeTrust()
         await(500)
         uiDevice.wakeUp()
-        await(500)
 
         trustAgentRule.agent.grantTrust(
             GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {}