Merge "Add description of regional preference" into udc-dev
diff --git a/core/api/current.txt b/core/api/current.txt
index 9f3ceb3..2161186 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -19370,7 +19370,7 @@
   public final class OutputConfiguration implements android.os.Parcelable {
     ctor public OutputConfiguration(@NonNull android.view.Surface);
     ctor public OutputConfiguration(int, @NonNull android.view.Surface);
-    ctor public OutputConfiguration(@NonNull android.util.Size, @NonNull Class<T>);
+    ctor public <T> OutputConfiguration(@NonNull android.util.Size, @NonNull Class<T>);
     method public void addSensorPixelModeUsed(int);
     method public void addSurface(@NonNull android.view.Surface);
     method @NonNull public static java.util.Collection<android.hardware.camera2.params.OutputConfiguration> createInstancesForMultiResolutionOutput(@NonNull android.hardware.camera2.MultiResolutionImageReader);
@@ -42066,6 +42066,7 @@
   }
 
   public final class CallControl {
+    method public void answer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
     method public void disconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
     method @NonNull public android.os.ParcelUuid getCallId();
     method public void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index ef5cd93..440ee20 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5758,7 +5758,7 @@
             List<Notification.Action> nonContextualActions = getNonContextualActions();
 
             int numActions = Math.min(nonContextualActions.size(), MAX_ACTION_BUTTONS);
-            boolean emphazisedMode = mN.fullScreenIntent != null
+            boolean emphasizedMode = mN.fullScreenIntent != null
                     || p.mCallStyleActions
                     || ((mN.flags & FLAG_FSI_REQUESTED_BUT_DENIED) != 0);
 
@@ -5771,7 +5771,7 @@
                 big.setInt(R.id.actions, "setCollapsibleIndentDimen",
                         R.dimen.call_notification_collapsible_indent);
             }
-            big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode);
+            big.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
             if (numActions > 0 && !p.mHideActions) {
                 big.setViewVisibility(R.id.actions_container, View.VISIBLE);
                 big.setViewVisibility(R.id.actions, View.VISIBLE);
@@ -5783,12 +5783,12 @@
                     boolean actionHasValidInput = hasValidRemoteInput(action);
                     validRemoteInput |= actionHasValidInput;
 
-                    final RemoteViews button = generateActionButton(action, emphazisedMode, p);
-                    if (actionHasValidInput && !emphazisedMode) {
+                    final RemoteViews button = generateActionButton(action, emphasizedMode, p);
+                    if (actionHasValidInput && !emphasizedMode) {
                         // Clear the drawable
                         button.setInt(R.id.action0, "setBackgroundResource", 0);
                     }
-                    if (emphazisedMode && i > 0) {
+                    if (emphasizedMode && i > 0) {
                         // Clear start margin from non-first buttons to reduce the gap between them.
                         //  (8dp remaining gap is from all buttons' standard 4dp inset).
                         button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0);
@@ -7475,10 +7475,10 @@
             Resources resources = context.getResources();
             boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
             if (mPictureIcon != null) {
-                int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
+                int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
                         ? R.dimen.notification_big_picture_max_height_low_ram
                         : R.dimen.notification_big_picture_max_height);
-                int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
+                int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
                         ? R.dimen.notification_big_picture_max_width_low_ram
                         : R.dimen.notification_big_picture_max_width);
                 mPictureIcon.scaleDownIfNecessary(maxPictureWidth, maxPictureHeight);
diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java
index 67634dc..3c10e81 100644
--- a/core/java/android/credentials/ui/IntentFactory.java
+++ b/core/java/android/credentials/ui/IntentFactory.java
@@ -78,7 +78,7 @@
                         Resources.getSystem()
                                 .getString(
                                         com.android.internal.R.string
-                                                .config_credentialManagerDialogComponent));
+                                                .config_credentialManagerReceiverComponent));
         intent.setComponent(componentName);
         intent.setAction(Constants.CREDMAN_ENABLED_PROVIDERS_UPDATED);
         return intent;
diff --git a/core/java/android/os/CancellationSignalBeamer.java b/core/java/android/os/CancellationSignalBeamer.java
new file mode 100644
index 0000000..afb5ff7
--- /dev/null
+++ b/core/java/android/os/CancellationSignalBeamer.java
@@ -0,0 +1,325 @@
+/*
+ * 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 android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.system.SystemCleaner;
+
+import java.lang.ref.Cleaner;
+import java.lang.ref.Reference;
+import java.util.HashMap;
+
+/**
+ * A transport for {@link CancellationSignal}, but unlike
+ * {@link CancellationSignal#createTransport()} doesn't require pre-creating the transport in the
+ * target process. Instead, cancellation is forwarded over the same IPC surface as the cancellable
+ * request.
+ *
+ * <p><strong>Important:</strong> For this to work, the following invariants must be held up:
+ * <ul>
+ *     <li>A call to beam() <strong>MUST</strong> result in a call to close() on the result
+ *     (otherwise, the token will be leaked and cancellation isn't propagated), and that call
+ *     must happen after the call using the
+ *     token is sent (otherwise, any concurrent cancellation may be lost). It is strongly
+ *     recommended to use try-with-resources on the token.
+ *     <li>The cancel(), forget() and cancellable operations transporting the token must either
+ *     all be oneway on the same binder, or all be non-oneway to guarantee proper ordering.
+ *     <li>A {@link CancellationSignal} <strong>SHOULD</strong> be used only once, as there
+ *     can only be a single {@link android.os.CancellationSignal.OnCancelListener OnCancelListener}.
+ *
+ * </ul>
+ * <p>Caveats:
+ * <ul>
+ *     <li>Cancellation is only ever dispatched after the token is closed, and thus after the
+ *     call performing the cancellable operation (if the invariants are followed). The operation
+ *     must therefore not block the incoming binder thread, or cancellation won't be possible.
+ *     <li>Consequently, in the unlikely event that the sender dies right after beaming an already
+ *     cancelled {@link CancellationSignal}, the cancellation may be lost (unlike with
+ *     {@link CancellationSignal#createTransport()}).
+ *     <li>The forwarding OnCancelListener is set in the implied finally phase of try-with-resources
+ *         / when closing the token. If the receiver is in the same process, and the signal is
+ *         already cancelled, this may invoke the target's OnCancelListener during that phase.
+ * </ul>
+ *
+ *
+ * <p>Usage:
+ * <pre>
+ *  // Sender:
+ *
+ *  class FooManager {
+ *    var mCancellationSignalSender = new CancellationSignalBeamer.Sender() {
+ *      &#064;Override
+ *      public void onCancel(IBinder token) { remoteIFooService.onCancelToken(token); }
+ *
+ *      &#064;Override
+ *      public void onForget(IBinder token) { remoteIFooService.onForgetToken(token); }
+ *    };
+ *
+ *    public void doCancellableOperation(..., CancellationSignal cs) {
+ *      try (var csToken = mCancellationSignalSender.beam(cs)) {
+ *          remoteIFooService.doCancellableOperation(..., csToken);
+ *      }
+ *    }
+ *  }
+ *
+ *  // Receiver:
+ *
+ *  class FooManagerService extends IFooService.Stub {
+ *    var mCancellationSignalReceiver = new CancellationSignalBeamer.Receiver();
+ *
+ *    &#064;Override
+ *    public void doCancellableOperation(..., IBinder csToken) {
+ *      CancellationSignal cs = mCancellationSignalReceiver.unbeam(csToken))
+ *      // ...
+ *    }
+ *
+ *    &#064;Override
+ *    public void onCancelToken(..., IBinder csToken) {
+ *      mCancellationSignalReceiver.cancelToken(csToken))
+ *    }
+ *
+ *    &#064;Override
+ *    public void onForgetToken(..., IBinder csToken) {
+ *      mCancellationSignalReceiver.forgetToken(csToken))
+ *    }
+ *  }
+ *
+ * </pre>
+ *
+ * @hide
+ */
+public class CancellationSignalBeamer {
+
+    static final Cleaner sCleaner = SystemCleaner.cleaner();
+
+    /** The sending side of an {@link CancellationSignalBeamer} */
+    public abstract static class Sender {
+
+        /**
+         * Beams a {@link CancellationSignal} through an existing Binder interface.
+         *
+         * @param cs the {@code CancellationSignal} to beam, or {@code null}.
+         * @return an {@link IBinder} token. MUST be {@link CloseableToken#close}d <em>after</em>
+         *         the binder call transporting it to the remote process, best with
+         *         try-with-resources. {@code null} if {@code cs} was {@code null}.
+         */
+        // TODO(b/254888024): @MustBeClosed
+        @Nullable
+        public CloseableToken beam(@Nullable CancellationSignal cs) {
+            if (cs == null) {
+                return null;
+            }
+            return new Token(this, cs);
+        }
+
+        /**
+         * A {@link #beam}ed {@link CancellationSignal} was closed.
+         *
+         * MUST be forwarded to {@link Receiver#cancel} with proper ordering. See
+         * {@link CancellationSignalBeamer} for details.
+         */
+        public abstract void onCancel(IBinder token);
+
+        /**
+         * A {@link #beam}ed {@link CancellationSignal} was GC'd.
+         *
+         * MUST be forwarded to {@link Receiver#forget} with proper ordering. See
+         * {@link CancellationSignalBeamer} for details.
+         */
+        public abstract void onForget(IBinder token);
+
+        private static class Token extends Binder implements CloseableToken, Runnable {
+
+            private final Sender mSender;
+            private Preparer mPreparer;
+
+            private Token(Sender sender, CancellationSignal signal) {
+                mSender = sender;
+                mPreparer = new Preparer(sender, signal, this);
+            }
+
+            @Override
+            public void close() {
+                Preparer preparer = mPreparer;
+                mPreparer = null;
+                if (preparer != null) {
+                    preparer.setup();
+                }
+            }
+
+            @Override
+            public void run() {
+                mSender.onForget(this);
+            }
+
+            private static class Preparer implements CancellationSignal.OnCancelListener {
+                private final Sender mSender;
+                private final CancellationSignal mSignal;
+                private final Token mToken;
+
+                private Preparer(Sender sender, CancellationSignal signal, Token token) {
+                    mSender = sender;
+                    mSignal = signal;
+                    mToken = token;
+                }
+
+                void setup() {
+                    sCleaner.register(this, mToken);
+                    mSignal.setOnCancelListener(this);
+                }
+
+                @Override
+                public void onCancel() {
+                    try {
+                        mSender.onCancel(mToken);
+                    } finally {
+                        // Make sure we dispatch onCancel before the cleaner can run.
+                        Reference.reachabilityFence(this);
+                    }
+                }
+            }
+        }
+
+        /**
+         * A {@link #beam}ed {@link CancellationSignal} ready for sending over Binder.
+         *
+         * MUST be closed <em>after</em> it is sent over binder, ideally through try-with-resources.
+         */
+        public interface CloseableToken extends IBinder, AutoCloseable {
+            @Override
+            void close(); // No throws
+        }
+    }
+
+    /** The receiving side of a {@link CancellationSignalBeamer}. */
+    public static class Receiver implements IBinder.DeathRecipient {
+        private final HashMap<IBinder, CancellationSignal> mTokenMap = new HashMap<>();
+        private final boolean mCancelOnSenderDeath;
+
+        /**
+         * Constructs a new {@code Receiver}.
+         *
+         * @param cancelOnSenderDeath if true, {@link CancellationSignal}s obtained from
+         *   {@link #unbeam} are automatically {@link #cancel}led if the sender token
+         *   {@link Binder#linkToDeath dies}; otherwise they are simnply dropped. Note: if the
+         *   sending process drops all references to the {@link CancellationSignal} before
+         *   process death, the cancellation is not guaranteed.
+         */
+        public Receiver(boolean cancelOnSenderDeath) {
+            mCancelOnSenderDeath = cancelOnSenderDeath;
+        }
+
+        /**
+         * Unbeams a token that was obtained via {@link Sender#beam} and turns it back into a
+         * {@link CancellationSignal}.
+         *
+         * A subsequent call to {@link #cancel} with the same token will cancel the returned
+         * {@code CancellationSignal}.
+         *
+         * @param token a token that was obtained from {@link Sender}, possibly in a remote process.
+         * @return a {@link CancellationSignal} linked to the given token.
+         */
+        @Nullable
+        public CancellationSignal unbeam(@Nullable IBinder token) {
+            if (token == null) {
+                return null;
+            }
+            synchronized (this) {
+                CancellationSignal cs = mTokenMap.get(token);
+                if (cs != null) {
+                    return cs;
+                }
+
+                cs = new CancellationSignal();
+                mTokenMap.put(token, cs);
+                try {
+                    token.linkToDeath(this, 0);
+                } catch (RemoteException e) {
+                    dead(token);
+                }
+                return cs;
+            }
+        }
+
+        /**
+         * Forgets state associated with the given token (if any).
+         *
+         * Subsequent calls to {@link #cancel} or binder death notifications on the token will not
+         * have any effect.
+         *
+         * This MUST be invoked when forwarding {@link Sender#onForget}, otherwise the token and
+         * {@link CancellationSignal} will leak if the token was ever {@link #unbeam}ed.
+         *
+         * Optionally, the receiving service logic may also invoke this if it can guarantee that
+         * the unbeamed CancellationSignal isn't needed anymore (i.e. the cancellable operation
+         * using the CancellationSignal has been fully completed).
+         *
+         * @param token the token to forget. No-op if {@code null}.
+         */
+        public void forget(@Nullable IBinder token) {
+            synchronized (this) {
+                if (mTokenMap.remove(token) != null) {
+                    token.unlinkToDeath(this, 0);
+                }
+            }
+        }
+
+        /**
+         * Cancels the {@link CancellationSignal} associated with the given token (if any).
+         *
+         * This MUST be invoked when forwarding {@link Sender#onCancel}, otherwise the token and
+         * {@link CancellationSignal} will leak if the token was ever {@link #unbeam}ed.
+         *
+         * Optionally, the receiving service logic may also invoke this if it can guarantee that
+         * the unbeamed CancellationSignal isn't needed anymore (i.e. the cancellable operation
+         * using the CancellationSignal has been fully completed).
+         *
+         * @param token the token to forget. No-op if {@code null}.
+         */
+        public void cancel(@Nullable IBinder token) {
+            CancellationSignal cs;
+            synchronized (this) {
+                cs = mTokenMap.get(token);
+                if (cs != null) {
+                    forget(token);
+                } else {
+                    return;
+                }
+            }
+            cs.cancel();
+        }
+
+        private void dead(@NonNull IBinder token) {
+            if (mCancelOnSenderDeath) {
+                cancel(token);
+            } else {
+                forget(token);
+            }
+        }
+
+        @Override
+        public void binderDied(@NonNull IBinder who) {
+            dead(who);
+        }
+
+        @Override
+        public void binderDied() {
+            throw new RuntimeException("unreachable");
+        }
+    }
+}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 12cd523..f53abce 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -2198,6 +2198,11 @@
                 }
                 mCreated = false;
             }
+
+            if (mSurfaceControl != null) {
+                mSurfaceControl.release();
+                mSurfaceControl = null;
+            }
         }
 
         private final DisplayListener mDisplayListener = new DisplayListener() {
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 20be9d6..5476088 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1261,20 +1261,17 @@
 
     /**
      * @hide
-     * Returns the display's HDR supported types.
+     * Returns the current mode's supported HDR types.
      *
      * @see #isHdr()
-     * @see HdrCapabilities#getSupportedHdrTypes()
+     * @see Mode#getSupportedHdrTypes()
      */
     @TestApi
     @NonNull
     public int[] getReportedHdrTypes() {
         synchronized (mLock) {
             updateDisplayInfoLocked();
-            if (mDisplayInfo.hdrCapabilities == null) {
-                return new int[0];
-            }
-            return mDisplayInfo.hdrCapabilities.getSupportedHdrTypes();
+            return mDisplayInfo.getMode().getSupportedHdrTypes();
         }
     }
 
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index bdc7333..aef0e65 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -899,9 +899,10 @@
 
         // 3. Get the activity names substring between the indexes
         final int activityStringStartIndex = packageInStringIndex + packageName.length() + 1;
-        if (activityStringStartIndex < firstNextSemicolonIndex) {
+        if (activityStringStartIndex >= firstNextSemicolonIndex) {
             Log.e(TAG, "Failed to get denied activity names from denylist because it's wrongly "
                     + "formatted");
+            return;
         }
         final String activitySubstring =
                 denyListString.substring(activityStringStartIndex, firstNextSemicolonIndex);
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index db17a53..d84acc0 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -25,6 +25,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.content.Context;
+import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -33,6 +34,7 @@
 import android.window.ImeOnBackInvokedDispatcher;
 
 import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.internal.inputmethod.IImeTracker;
 import com.android.internal.inputmethod.IInputMethodClient;
 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
 import com.android.internal.inputmethod.IRemoteInputConnection;
@@ -40,7 +42,6 @@
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.inputmethod.StartInputFlags;
 import com.android.internal.inputmethod.StartInputReason;
-import com.android.internal.view.IImeTracker;
 import com.android.internal.view.IInputMethodManager;
 
 import java.util.ArrayList;
@@ -581,51 +582,57 @@
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onRequestShow */
     @AnyThread
-    @Nullable
-    static IBinder onRequestShow(int uid, @ImeTracker.Origin int origin,
-            @SoftInputShowHideReason int reason) {
+    @NonNull
+    static ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
+            @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
         final IImeTracker service = getImeTrackerService();
         if (service == null) {
-            return null;
+            // Create token with "fake" binder if the service was not found.
+            return new ImeTracker.Token(new Binder(), tag);
         }
         try {
-            return service.onRequestShow(uid, origin, reason);
+            return service.onRequestShow(tag, uid, origin, reason);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onRequestHide */
     @AnyThread
-    @Nullable
-    static IBinder onRequestHide(int uid, @ImeTracker.Origin int origin,
-            @SoftInputShowHideReason int reason) {
+    @NonNull
+    static ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
+            @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
         final IImeTracker service = getImeTrackerService();
         if (service == null) {
-            return null;
+            // Create token with "fake" binder if the service was not found.
+            return new ImeTracker.Token(new Binder(), tag);
         }
         try {
-            return service.onRequestHide(uid, origin, reason);
+            return service.onRequestHide(tag, uid, origin, reason);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onProgress */
     @AnyThread
-    static void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+    static void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) {
         final IImeTracker service = getImeTrackerService();
         if (service == null) {
             return;
         }
         try {
-            service.onProgress(statsToken, phase);
+            service.onProgress(binder, phase);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onFailed */
     @AnyThread
-    static void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+    static void onFailed(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) {
         final IImeTracker service = getImeTrackerService();
         if (service == null) {
             return;
@@ -637,8 +644,9 @@
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onCancelled */
     @AnyThread
-    static void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+    static void onCancelled(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) {
         final IImeTracker service = getImeTrackerService();
         if (service == null) {
             return;
@@ -650,8 +658,9 @@
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onShown */
     @AnyThread
-    static void onShown(@NonNull IBinder statsToken) {
+    static void onShown(@NonNull ImeTracker.Token statsToken) {
         final IImeTracker service = getImeTrackerService();
         if (service == null) {
             return;
@@ -663,8 +672,9 @@
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onHidden */
     @AnyThread
-    static void onHidden(@NonNull IBinder statsToken) {
+    static void onHidden(@NonNull ImeTracker.Token statsToken) {
         final IImeTracker service = getImeTrackerService();
         if (service == null) {
             return;
@@ -676,6 +686,7 @@
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#hasPendingImeVisibilityRequests */
     @AnyThread
     @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
     static boolean hasPendingImeVisibilityRequests() {
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index e5a99ff..f0d1019 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -26,7 +26,6 @@
 import android.annotation.Nullable;
 import android.app.ActivityThread;
 import android.content.Context;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -47,7 +46,7 @@
 import java.util.Arrays;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
 import java.util.stream.Collectors;
 
 /** @hide */
@@ -321,9 +320,8 @@
     /**
      * Creates an IME show request tracking token.
      *
-     * @param component the component name where the IME show request was created,
-     *                  or {@code null} otherwise
-     *                  (defaulting to {@link ActivityThread#currentProcessName()}).
+     * @param component the name of the component that created the IME request, or {@code null}
+     *                  otherwise (defaulting to {@link ActivityThread#currentProcessName()}).
      * @param uid the uid of the client that requested the IME.
      * @param origin the origin of the IME show request.
      * @param reason the reason why the IME show request was created.
@@ -337,9 +335,8 @@
     /**
      * Creates an IME hide request tracking token.
      *
-     * @param component the component name where the IME hide request was created,
-     *                  or {@code null} otherwise
-     *                  (defaulting to {@link ActivityThread#currentProcessName()}).
+     * @param component the name of the component that created the IME request, or {@code null}
+     *                  otherwise (defaulting to {@link ActivityThread#currentProcessName()}).
      * @param uid the uid of the client that requested the IME.
      * @param origin the origin of the IME hide request.
      * @param reason the reason why the IME hide request was created.
@@ -435,8 +432,7 @@
             mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false);
             // Update logging flag dynamically.
             SystemProperties.addChangeCallback(() ->
-                    mLogProgress =
-                            SystemProperties.getBoolean("persist.debug.imetracker", false));
+                    mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false));
         }
 
         /** Whether progress should be logged. */
@@ -446,10 +442,9 @@
         @Override
         public Token onRequestShow(@Nullable String component, int uid, @Origin int origin,
                 @SoftInputShowHideReason int reason) {
-            IBinder binder = IInputMethodManagerGlobalInvoker.onRequestShow(uid, origin, reason);
-            if (binder == null) binder = new Binder();
-
-            final Token token = Token.build(binder, component);
+            final var tag = getTag(component);
+            final var token = IInputMethodManagerGlobalInvoker.onRequestShow(tag, uid, origin,
+                    reason);
 
             Log.i(TAG, token.mTag + ": onRequestShow at " + Debug.originToString(origin)
                     + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
@@ -461,10 +456,9 @@
         @Override
         public Token onRequestHide(@Nullable String component, int uid, @Origin int origin,
                 @SoftInputShowHideReason int reason) {
-            IBinder binder = IInputMethodManagerGlobalInvoker.onRequestHide(uid, origin, reason);
-            if (binder == null) binder = new Binder();
-
-            final Token token = Token.build(binder, component);
+            final var tag = getTag(component);
+            final var token = IInputMethodManagerGlobalInvoker.onRequestHide(tag, uid, origin,
+                    reason);
 
             Log.i(TAG, token.mTag + ": onRequestHide at " + Debug.originToString(origin)
                     + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
@@ -485,7 +479,7 @@
         @Override
         public void onFailed(@Nullable Token token, @Phase int phase) {
             if (token == null) return;
-            IInputMethodManagerGlobalInvoker.onFailed(token.mBinder, phase);
+            IInputMethodManagerGlobalInvoker.onFailed(token, phase);
 
             Log.i(TAG, token.mTag + ": onFailed at " + Debug.phaseToString(phase));
         }
@@ -499,7 +493,7 @@
         @Override
         public void onCancelled(@Nullable Token token, @Phase int phase) {
             if (token == null) return;
-            IInputMethodManagerGlobalInvoker.onCancelled(token.mBinder, phase);
+            IInputMethodManagerGlobalInvoker.onCancelled(token, phase);
 
             Log.i(TAG, token.mTag + ": onCancelled at " + Debug.phaseToString(phase));
         }
@@ -507,7 +501,7 @@
         @Override
         public void onShown(@Nullable Token token) {
             if (token == null) return;
-            IInputMethodManagerGlobalInvoker.onShown(token.mBinder);
+            IInputMethodManagerGlobalInvoker.onShown(token);
 
             Log.i(TAG, token.mTag + ": onShown");
         }
@@ -515,10 +509,24 @@
         @Override
         public void onHidden(@Nullable Token token) {
             if (token == null) return;
-            IInputMethodManagerGlobalInvoker.onHidden(token.mBinder);
+            IInputMethodManagerGlobalInvoker.onHidden(token);
 
             Log.i(TAG, token.mTag + ": onHidden");
         }
+
+        /**
+         * Returns a logging tag using the given component name.
+         *
+         * @param component the name of the component that created the IME request, or {@code null}
+         *                  otherwise (defaulting to {@link ActivityThread#currentProcessName()}).
+         */
+        @NonNull
+        private String getTag(@Nullable String component) {
+            if (component == null) {
+                component = ActivityThread.currentProcessName();
+            }
+            return component + ":" + Integer.toHexString(ThreadLocalRandom.current().nextInt());
+        }
     };
 
     /** The singleton IME tracker instance for instrumenting jank metrics. */
@@ -528,28 +536,31 @@
     ImeLatencyTracker LATENCY_TRACKER = new ImeLatencyTracker();
 
     /** A token that tracks the progress of an IME request. */
-    class Token implements Parcelable {
+    final class Token implements Parcelable {
 
+        /** The binder used to identify this token. */
         @NonNull
-        public final IBinder mBinder;
+        private final IBinder mBinder;
 
+        /** Logging tag, of the shape "component:random_hexadecimal". */
         @NonNull
         private final String mTag;
 
-        @NonNull
-        private static Token build(@NonNull IBinder binder, @Nullable String component) {
-            if (component == null) component = ActivityThread.currentProcessName();
-            final String tag = component + ":" + Integer.toHexString((new Random().nextInt()));
-
-            return new Token(binder, tag);
-        }
-
-        private Token(@NonNull IBinder binder, @NonNull String tag) {
+        public Token(@NonNull IBinder binder, @NonNull String tag) {
             mBinder = binder;
             mTag = tag;
         }
 
-        /** Returns the {@link Token#mTag} */
+        private Token(@NonNull Parcel in) {
+            mBinder = in.readStrongBinder();
+            mTag = in.readString8();
+        }
+
+        @NonNull
+        public IBinder getBinder() {
+            return mBinder;
+        }
+
         @NonNull
         public String getTag() {
             return mTag;
@@ -562,7 +573,7 @@
         }
 
         @Override
-        public void writeToParcel(Parcel dest, int flags) {
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeStrongBinder(mBinder);
             dest.writeString8(mTag);
         }
@@ -571,12 +582,11 @@
         public static final Creator<Token> CREATOR = new Creator<>() {
             @NonNull
             @Override
-            public Token createFromParcel(Parcel source) {
-                final IBinder binder = source.readStrongBinder();
-                final String tag = source.readString8();
-                return new Token(binder, tag);
+            public Token createFromParcel(@NonNull Parcel in) {
+                return new Token(in);
             }
 
+            @NonNull
             @Override
             public Token[] newArray(int size) {
                 return new Token[size];
@@ -589,40 +599,50 @@
      *
      * Note: This is held in a separate class so that it only gets initialized when actually needed.
      */
-    class Debug {
+    final class Debug {
 
+        @NonNull
         private static final Map<Integer, String> sTypes =
                 getFieldMapping(ImeTracker.class, "TYPE_");
+        @NonNull
         private static final Map<Integer, String> sStatus =
                 getFieldMapping(ImeTracker.class, "STATUS_");
+        @NonNull
         private static final Map<Integer, String> sOrigins =
                 getFieldMapping(ImeTracker.class, "ORIGIN_");
+        @NonNull
         private static final Map<Integer, String> sPhases =
                 getFieldMapping(ImeTracker.class, "PHASE_");
 
+        @NonNull
         public static String typeToString(@Type int type) {
             return sTypes.getOrDefault(type, "TYPE_" + type);
         }
 
+        @NonNull
         public static String statusToString(@Status int status) {
             return sStatus.getOrDefault(status, "STATUS_" + status);
         }
 
+        @NonNull
         public static String originToString(@Origin int origin) {
             return sOrigins.getOrDefault(origin, "ORIGIN_" + origin);
         }
 
+        @NonNull
         public static String phaseToString(@Phase int phase) {
             return sPhases.getOrDefault(phase, "PHASE_" + phase);
         }
 
-        private static Map<Integer, String> getFieldMapping(Class<?> cls, String fieldPrefix) {
+        @NonNull
+        private static Map<Integer, String> getFieldMapping(Class<?> cls,
+                @NonNull String fieldPrefix) {
             return Arrays.stream(cls.getDeclaredFields())
                     .filter(field -> field.getName().startsWith(fieldPrefix))
                     .collect(Collectors.toMap(Debug::getFieldValue, Field::getName));
         }
 
-        private static int getFieldValue(Field field) {
+        private static int getFieldValue(@NonNull Field field) {
             try {
                 return field.getInt(null);
             } catch (IllegalAccessException e) {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index b89dd31..9f9a781 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -52,6 +52,7 @@
 import android.graphics.RenderNode;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.LocaleList;
@@ -3238,6 +3239,44 @@
                 .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
 
         mPreserveSelection = true;
+
+        // No-op for the old context menu because it doesn't have icons.
+        adjustIconSpacing(menu);
+    }
+
+    /**
+     * Adjust icon spacing to align the texts.
+     * @hide
+     */
+    @VisibleForTesting
+    public void adjustIconSpacing(ContextMenu menu) {
+        int width = -1;
+        int height = -1;
+        for (int i = 0; i < menu.size(); ++i) {
+            final MenuItem item = menu.getItem(i);
+            final Drawable d = item.getIcon();
+            if (d == null) {
+                continue;
+            }
+
+            width = Math.max(width, d.getIntrinsicWidth());
+            height = Math.max(height, d.getIntrinsicHeight());
+        }
+
+        if (width < 0 || height < 0) {
+            return;  // No menu has icon drawable.
+        }
+
+        GradientDrawable paddingDrawable = new GradientDrawable();
+        paddingDrawable.setSize(width, height);
+
+        for (int i = 0; i < menu.size(); ++i) {
+            final MenuItem item = menu.getItem(i);
+            final Drawable d = item.getIcon();
+            if (d == null) {
+                item.setIcon(paddingDrawable);
+            }
+        }
     }
 
     @Nullable
diff --git a/core/java/com/android/internal/view/IImeTracker.aidl b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
similarity index 71%
rename from core/java/com/android/internal/view/IImeTracker.aidl
rename to core/java/com/android/internal/inputmethod/IImeTracker.aidl
index b062ca7..c7418ee 100644
--- a/core/java/com/android/internal/view/IImeTracker.aidl
+++ b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
@@ -14,43 +14,45 @@
  * limitations under the License.
  */
 
-package com.android.internal.view;
+package com.android.internal.inputmethod;
 
 import android.view.inputmethod.ImeTracker;
 
 /**
- * Interface to the global Ime tracker, used by all client applications.
+ * Interface to the global IME tracker service, used by all client applications.
  * {@hide}
  */
 interface IImeTracker {
 
     /**
-     * Called when an IME show request is created,
-     * returns a new Binder to be associated with the IME tracking token.
+     * Called when an IME show request is created.
      *
+     * @param tag the logging tag.
      * @param uid the uid of the client that requested the IME.
      * @param origin the origin of the IME show request.
      * @param reason the reason why the IME show request was created.
+     * @return A new IME tracking token.
      */
-    IBinder onRequestShow(int uid, int origin, int reason);
+    ImeTracker.Token onRequestShow(String tag, int uid, int origin, int reason);
 
     /**
-     * Called when an IME hide request is created,
-     * returns a new Binder to be associated with the IME tracking token.
+     * Called when an IME hide request is created.
      *
+     * @param tag the logging tag.
      * @param uid the uid of the client that requested the IME.
      * @param origin the origin of the IME hide request.
      * @param reason the reason why the IME hide request was created.
+     * @return A new IME tracking token.
      */
-    IBinder onRequestHide(int uid, int origin, int reason);
+    ImeTracker.Token onRequestHide(String tag, int uid, int origin, int reason);
 
     /**
      * Called when the IME request progresses to a further phase.
      *
-     * @param statsToken the token tracking the current IME request.
+     * @param binder the binder of token tracking the current IME request.
      * @param phase the new phase the IME request reached.
      */
-    oneway void onProgress(in IBinder statsToken, int phase);
+    oneway void onProgress(in IBinder binder, int phase);
 
     /**
      * Called when the IME request fails.
@@ -58,7 +60,7 @@
      * @param statsToken the token tracking the current IME request.
      * @param phase the phase the IME request failed at.
      */
-    oneway void onFailed(in IBinder statsToken, int phase);
+    oneway void onFailed(in ImeTracker.Token statsToken, int phase);
 
     /**
      * Called when the IME request is cancelled.
@@ -66,21 +68,21 @@
      * @param statsToken the token tracking the current IME request.
      * @param phase the phase the IME request was cancelled at.
      */
-    oneway void onCancelled(in IBinder statsToken, int phase);
+    oneway void onCancelled(in ImeTracker.Token statsToken, int phase);
 
     /**
      * Called when the IME show request is successful.
      *
      * @param statsToken the token tracking the current IME request.
      */
-    oneway void onShown(in IBinder statsToken);
+    oneway void onShown(in ImeTracker.Token statsToken);
 
     /**
      * Called when the IME hide request is successful.
      *
      * @param statsToken the token tracking the current IME request.
      */
-    oneway void onHidden(in IBinder statsToken);
+    oneway void onHidden(in ImeTracker.Token statsToken);
 
     /**
      * Checks whether there are any pending IME visibility requests.
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 5805d0e..9a4610e 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -23,11 +23,11 @@
 import android.view.inputmethod.EditorInfo;
 import android.window.ImeOnBackInvokedDispatcher;
 
+import com.android.internal.inputmethod.IImeTracker;
 import com.android.internal.inputmethod.IInputMethodClient;
 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
 import com.android.internal.inputmethod.IRemoteInputConnection;
 import com.android.internal.inputmethod.InputBindResult;
-import com.android.internal.view.IImeTracker;
 
 /**
  * Public interface to the global input method manager, used by all client
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 12eff67..2f94ed7 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3065,6 +3065,10 @@
     <string name="config_credentialManagerDialogComponent" translatable="false"
             >com.android.credentialmanager/com.android.credentialmanager.CredentialSelectorActivity</string>
 
+    <!-- Name of the broadcast receiver that is used to receive provider change events -->
+    <string name="config_credentialManagerReceiverComponent" translatable="false"
+            >com.android.credentialmanager/com.android.credentialmanager.CredentialProviderReceiver</string>
+
     <!-- Apps that are authorized to access shared accounts, overridden by product overlays -->
     <string name="config_appsAuthorizedForSharedAccounts" translatable="false">;com.android.settings;</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7e89fc8..12646a0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2210,6 +2210,7 @@
   <java-symbol type="string" name="config_platformVpnConfirmDialogComponent" />
   <java-symbol type="string" name="config_carrierAppInstallDialogComponent" />
   <java-symbol type="string" name="config_credentialManagerDialogComponent" />
+  <java-symbol type="string" name="config_credentialManagerReceiverComponent" />
   <java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
   <java-symbol type="string" name="config_persistentDataPackageName" />
   <java-symbol type="string" name="config_deviceConfiguratorPackageName" />
diff --git a/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java b/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java
new file mode 100644
index 0000000..42c97f3
--- /dev/null
+++ b/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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 android.os;
+
+import static android.os.CancellationSignalBeamer.Sender;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.CancellationSignalBeamer.Receiver;
+import android.util.PollingCheck;
+import android.util.PollingCheck.PollingCheckCondition;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.Cleaner;
+import java.lang.ref.Reference;
+import java.util.concurrent.CountDownLatch;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CancellationSignalBeamerTest {
+
+    private CancellationSignal mSenderSignal = new CancellationSignal();
+    private CancellationSignal mReceivedSignal;
+    private Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+    @Test
+    public void testBeam_null() {
+        try (var token = mSender.beam(null)) {
+            assertThat(token).isNull();
+            invokeGenericService(token);
+        }
+        assertThat(mReceivedSignal).isNull();
+    }
+
+    @Test
+    public void testBeam_nonNull() {
+        try (var token = mSender.beam(mSenderSignal)) {
+            assertThat(token).isNotNull();
+            invokeGenericService(token);
+        }
+        assertThat(mReceivedSignal).isNotNull();
+    }
+
+    @Test
+    public void testBeam_async() {
+        IBinder outerToken;
+        try (var token = mSender.beam(mSenderSignal)) {
+            assertThat(token).isNotNull();
+            outerToken = token;
+        }
+        invokeGenericService(outerToken);
+        assertThat(mReceivedSignal).isNotNull();
+    }
+
+    @Test
+    public void testCancelOnSentSignal_cancelsReceivedSignal() {
+        try (var token = mSender.beam(mSenderSignal)) {
+            invokeGenericService(token);
+        }
+        mSenderSignal.cancel();
+        assertThat(mReceivedSignal.isCanceled()).isTrue();
+    }
+
+    @Test
+    public void testSendingCancelledSignal_cancelsReceivedSignal() {
+        mSenderSignal.cancel();
+        try (var token = mSender.beam(mSenderSignal)) {
+            invokeGenericService(token);
+        }
+        assertThat(mReceivedSignal.isCanceled()).isTrue();
+    }
+
+    @Test
+    public void testUnbeam_null() {
+        assertThat(mReceiver.unbeam(null)).isNull();
+    }
+
+    @Test
+    public void testForget_null() {
+        mReceiver.forget(null);
+    }
+
+    @Test
+    public void testCancel_null() {
+        mReceiver.cancel(null);
+    }
+
+    @Test
+    public void testForget_withUnknownToken() {
+        mReceiver.forget(new Binder());
+    }
+
+    @Test
+    public void testCancel_withUnknownToken() {
+        mReceiver.cancel(new Binder());
+    }
+
+    @Test
+    public void testBinderDied_withUnknownToken() {
+        mReceiver.binderDied(new Binder());
+    }
+
+    @Test
+    public void testReceiverWithCancelOnSenderDead_cancelsOnSenderDeath() {
+        var receiver = new Receiver(true /* cancelOnSenderDeath */);
+        var token = new Binder();
+        var signal = receiver.unbeam(token);
+        receiver.binderDied(token);
+        assertThat(signal.isCanceled()).isTrue();
+    }
+
+    @Test
+    public void testReceiverWithoutCancelOnSenderDead_doesntCancelOnSenderDeath() {
+        var receiver = new Receiver(false /* cancelOnSenderDeath */);
+        var token = new Binder();
+        var signal = receiver.unbeam(token);
+        receiver.binderDied(token);
+        assertThat(signal.isCanceled()).isFalse();
+    }
+
+    @Test
+    public void testDroppingSentSignal_dropsReceivedSignal() throws Exception {
+        // In a multiprocess scenario, sending token over Binder might leak the token
+        // on both ends if we create a reference cycle. Simulate that worst-case scenario
+        // here by leaking it directly, then test that cleanup of the signals still works.
+        var receivedSignalCleaned = new CountDownLatch(1);
+        var tokenRef = new Object[1];
+        // Reference the cancellation signals in a separate method scope, so we don't
+        // accidentally leak them on the stack / in a register.
+        Runnable r = () -> {
+            try (var token = mSender.beam(mSenderSignal)) {
+                tokenRef[0] = token;
+                invokeGenericService(token);
+            }
+            mSenderSignal = null;
+
+            Cleaner.create().register(mReceivedSignal, receivedSignalCleaned::countDown);
+            mReceivedSignal = null;
+        };
+        r.run();
+
+        waitForWithGc(() -> receivedSignalCleaned.getCount() == 0);
+
+        Reference.reachabilityFence(tokenRef[0]);
+    }
+
+    @Test
+    public void testRepeatedBeaming_doesntLeak() throws Exception {
+        var receivedSignalCleaned = new CountDownLatch(1);
+        var tokenRef = new Object[1];
+        // Reference the cancellation signals in a separate method scope, so we don't
+        // accidentally leak them on the stack / in a register.
+        Runnable r = () -> {
+            try (var token = mSender.beam(mSenderSignal)) {
+                tokenRef[0] = token;
+                invokeGenericService(token);
+            }
+            // Beaming again leaves mReceivedSignal dangling, so it should be collected.
+            mSender.beam(mSenderSignal).close();
+
+            Cleaner.create().register(mReceivedSignal, receivedSignalCleaned::countDown);
+            mReceivedSignal = null;
+        };
+        r.run();
+
+        waitForWithGc(() -> receivedSignalCleaned.getCount() == 0);
+
+        Reference.reachabilityFence(tokenRef[0]);
+    }
+
+    private void waitForWithGc(PollingCheckCondition condition) throws IOException {
+        try {
+            PollingCheck.waitFor(() -> {
+                Runtime.getRuntime().gc();
+                return condition.canProceed();
+            });
+        } catch (AssertionError e) {
+            File heap = new File(mContext.getExternalFilesDir(null), "dump.hprof");
+            Debug.dumpHprofData(heap.getAbsolutePath());
+            throw e;
+        }
+    }
+
+    private void invokeGenericService(IBinder cancellationSignalToken) {
+        mReceivedSignal = mReceiver.unbeam(cancellationSignalToken);
+    }
+
+    private final Sender mSender = new Sender() {
+        @Override
+        public void onCancel(IBinder token) {
+            mReceiver.cancel(token);
+        }
+
+        @Override
+        public void onForget(IBinder token) {
+            mReceiver.forget(token);
+        }
+    };
+
+    private final Receiver mReceiver = new Receiver(false);
+}
diff --git a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
index 0c7550e..777246b 100644
--- a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
@@ -33,6 +33,8 @@
 import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.Icon;
 import android.view.ContextMenu;
 import android.view.MenuItem;
@@ -167,4 +169,75 @@
         assertThat(idCaptor.getValue()).isEqualTo(TextView.ID_ASSIST);
         assertThat(titleCaptor.getValue().toString()).isEqualTo(ACTION_TITLE);
     }
+
+    @UiThreadTest
+    @Test
+    public void testAdjustIconSpaces() {
+        GradientDrawable gd = new GradientDrawable();
+        gd.setSize(128, 256);
+
+        // Setup mocks
+        ContextMenu menu = mock(ContextMenu.class);
+
+        MenuItem mockIconMenu = newMockMenuItem();
+        when(mockIconMenu.getIcon()).thenReturn(gd);
+
+        MenuItem mockNoIconMenu = newMockMenuItem();
+        when(mockNoIconMenu.getIcon()).thenReturn(null);
+
+        MenuItem mockNoIconMenu2 = newMockMenuItem();
+        when(mockNoIconMenu2.getIcon()).thenReturn(null);
+
+        when(menu.size()).thenReturn(3);
+        when(menu.getItem(0)).thenReturn(mockIconMenu);
+        when(menu.getItem(1)).thenReturn(mockNoIconMenu);
+        when(menu.getItem(2)).thenReturn(mockNoIconMenu2);
+
+
+        // Execute the test method
+        EditText et = mActivity.findViewById(R.id.editText);
+        Editor editor = et.getEditorForTesting();
+        editor.adjustIconSpacing(menu);
+
+        // Verify
+        ArgumentCaptor<Drawable> drawableCaptor = ArgumentCaptor.forClass(Drawable.class);
+        verify(mockNoIconMenu).setIcon(drawableCaptor.capture());
+
+        Drawable paddingDrawable = drawableCaptor.getValue();
+        assertThat(paddingDrawable).isNotNull();
+        assertThat(paddingDrawable.getIntrinsicWidth()).isEqualTo(128);
+        assertThat(paddingDrawable.getIntrinsicHeight()).isEqualTo(256);
+
+        ArgumentCaptor<Drawable> drawableCaptor2 = ArgumentCaptor.forClass(Drawable.class);
+        verify(mockNoIconMenu2).setIcon(drawableCaptor2.capture());
+
+        Drawable paddingDrawable2 = drawableCaptor2.getValue();
+        assertThat(paddingDrawable2).isSameInstanceAs(paddingDrawable);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testAdjustIconSpacesNoIconCase() {
+        // Setup mocks
+        ContextMenu menu = mock(ContextMenu.class);
+
+        MenuItem mockNoIconMenu = newMockMenuItem();
+        when(mockNoIconMenu.getIcon()).thenReturn(null);
+
+        MenuItem mockNoIconMenu2 = newMockMenuItem();
+        when(mockNoIconMenu2.getIcon()).thenReturn(null);
+
+        when(menu.size()).thenReturn(2);
+        when(menu.getItem(0)).thenReturn(mockNoIconMenu);
+        when(menu.getItem(1)).thenReturn(mockNoIconMenu2);
+
+        // Execute the test method
+        EditText et = mActivity.findViewById(R.id.editText);
+        Editor editor = et.getEditorForTesting();
+        editor.adjustIconSpacing(menu);
+
+        // Verify
+        verify(mockNoIconMenu, times(0)).setIcon(any());
+        verify(mockNoIconMenu2, times(0)).setIcon(any());
+    }
 }
diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTest.java b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
index 9ccf3b3..3b8b8c7 100644
--- a/core/tests/mockingcoretests/src/android/view/DisplayTest.java
+++ b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
@@ -117,6 +117,31 @@
     }
 
     @Test
+    public void testGetReportedHdrTypes_returns_mode_specific_hdr_types() {
+        setDisplayInfoPortrait(mDisplayInfo);
+        float[] alternativeRefreshRates = new float[0];
+        int[] hdrTypesWithDv = new int[] {1, 2, 3, 4};
+        Display.Mode modeWithDv = new Display.Mode(/* modeId= */ 0, 0, 0, 0f,
+                alternativeRefreshRates, hdrTypesWithDv);
+
+        int[] hdrTypesWithoutDv = new int[]{2, 3, 4};
+        Display.Mode modeWithoutDv = new Display.Mode(/* modeId= */ 1, 0, 0, 0f,
+                alternativeRefreshRates, hdrTypesWithoutDv);
+
+        mDisplayInfo.supportedModes = new Display.Mode[] {modeWithoutDv, modeWithDv};
+        mDisplayInfo.hdrCapabilities = new Display.HdrCapabilities(hdrTypesWithDv, 0, 0, 0);
+
+        final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+
+        mDisplayInfo.modeId = 0;
+        assertArrayEquals(hdrTypesWithDv, display.getReportedHdrTypes());
+
+        mDisplayInfo.modeId = 1;
+        assertArrayEquals(hdrTypesWithoutDv, display.getReportedHdrTypes());
+    }
+
+    @Test
     public void testConstructor_defaultDisplayAdjustments_matchesDisplayInfo() {
         setDisplayInfoPortrait(mDisplayInfo);
         final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
diff --git a/packages/CredentialManager/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml
index b36cb5c..dfc8aa0 100644
--- a/packages/CredentialManager/AndroidManifest.xml
+++ b/packages/CredentialManager/AndroidManifest.xml
@@ -41,6 +41,15 @@
         android:excludeFromRecents="true"
         android:theme="@style/Theme.CredentialSelector">
     </activity>
+
+    <receiver
+        android:name=".CredentialProviderReceiver"
+        android:exported="true"
+        android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR">
+        <intent-filter>
+            <action android:name="android.credentials.ui.action.CREDMAN_ENABLED_PROVIDERS_UPDATED"/>
+        </intent-filter>
+    </receiver>
   </application>
 
 </manifest>
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index e3236b3..30b97bf 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -73,7 +73,7 @@
         requestInfo = intent.extras?.getParcelable(
             RequestInfo.EXTRA_REQUEST_INFO,
             RequestInfo::class.java
-        ) ?: testCreatePasskeyRequestInfo()
+        ) ?: testCreatePasswordRequestInfo()
 
         providerEnabledList = when (requestInfo.type) {
             RequestInfo.TYPE_CREATE ->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt
new file mode 100644
index 0000000..ee8cffe
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import com.android.credentialmanager.common.Constants
+
+
+class CredentialProviderReceiver : BroadcastReceiver() {
+
+    override fun onReceive(context: Context?, intent: Intent?) {
+        Log.d(Constants.LOG_TAG, "Received intent in CredentialProviderReceiver")
+
+        val sharedPreferences = context?.getSharedPreferences(context?.packageName,
+                Context.MODE_PRIVATE)
+        sharedPreferences?.edit()?.remove(UserConfigRepo.DEFAULT_PROVIDER)?.commit()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notif_half_shelf.xml b/packages/SystemUI/res/layout/notif_half_shelf.xml
index 8ba1ff3..37b8ae0 100644
--- a/packages/SystemUI/res/layout/notif_half_shelf.xml
+++ b/packages/SystemUI/res/layout/notif_half_shelf.xml
@@ -76,7 +76,10 @@
                     android:layout_height="48dp"
                     android:layout_width="wrap_content"
                     android:layout_gravity="center_vertical"
-                    android:padding="8dp" />
+                    android:padding="8dp"
+                    android:track="@drawable/settingslib_track_selector"
+                    android:thumb="@drawable/settingslib_thumb_selector"
+                    android:theme="@style/MainSwitch.Settingslib"/>
             </com.android.systemui.statusbar.notification.row.AppControlView>
 
             <!-- ChannelRows get added dynamically -->
@@ -101,7 +104,7 @@
                 android:minWidth="@dimen/notification_importance_toggle_size"
                 android:minHeight="@dimen/notification_importance_toggle_size"
                 android:maxWidth="200dp"
-                style="@style/TextAppearance.NotificationInfo.Button"/>
+                style="@style/Widget.Dialog.Button"/>
             <TextView
                 android:id="@+id/done_button"
                 android:text="@string/inline_ok_button"
@@ -113,7 +116,7 @@
                 android:minWidth="@dimen/notification_importance_toggle_size"
                 android:minHeight="@dimen/notification_importance_toggle_size"
                 android:layout_alignParentEnd="true"
-                style="@style/TextAppearance.NotificationInfo.Button"/>
+                style="@style/Widget.Dialog.Button"/>
         </RelativeLayout>
     </LinearLayout>
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/notif_half_shelf_row.xml b/packages/SystemUI/res/layout/notif_half_shelf_row.xml
index d03cd7e..190f999 100644
--- a/packages/SystemUI/res/layout/notif_half_shelf_row.xml
+++ b/packages/SystemUI/res/layout/notif_half_shelf_row.xml
@@ -85,6 +85,9 @@
             android:layout_width="wrap_content"
             android:layout_gravity="center_vertical"
             android:padding="8dp"
+            android:track="@drawable/settingslib_track_selector"
+            android:thumb="@drawable/settingslib_thumb_selector"
+            android:theme="@style/MainSwitch.Settingslib"
         />
     </LinearLayout>
 </com.android.systemui.statusbar.notification.row.ChannelRow>
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index f32ea71..ab78b1b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -155,7 +155,7 @@
     // TODO(b/255618149): Tracking Bug
     @JvmField
     val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES =
-        unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = true)
+        releasedFlag(216, "customizable_lock_screen_quick_affordances")
 
     /** Shows chipbar UI whenever the device is unlocked by ActiveUnlock (watch). */
     // TODO(b/256513609): Tracking Bug
@@ -187,7 +187,7 @@
 
     // TODO(b/262780002): Tracking Bug
     @JvmField
-    val REVAMPED_WALLPAPER_UI = unreleasedFlag(222, "revamped_wallpaper_ui", teamfood = true)
+    val REVAMPED_WALLPAPER_UI = releasedFlag(222, "revamped_wallpaper_ui")
 
     /** A different path for unocclusion transitions back to keyguard */
     // TODO(b/262859270): Tracking Bug
@@ -214,10 +214,9 @@
     // TODO(b/266242192): Tracking Bug
     @JvmField
     val LOCK_SCREEN_LONG_PRESS_ENABLED =
-        unreleasedFlag(
+        releasedFlag(
             228,
-            "lock_screen_long_press_enabled",
-            teamfood = true,
+            "lock_screen_long_press_enabled"
         )
 
     // 300 - power menu
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
index f0b221d..0de3246 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
@@ -344,7 +344,7 @@
             or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
 }
 
-class ChannelEditorDialog(context: Context) : Dialog(context) {
+class ChannelEditorDialog(context: Context, theme: Int) : Dialog(context, theme) {
     fun updateDoneButtonText(hasChanges: Boolean) {
         findViewById<TextView>(R.id.done_button)?.setText(
                 if (hasChanges)
@@ -361,7 +361,7 @@
         }
 
         fun build(): ChannelEditorDialog {
-            return ChannelEditorDialog(context)
+            return ChannelEditorDialog(context, R.style.Theme_SystemUI_Dialog)
         }
     }
 }
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 b4dcf43..f650560 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -51,6 +51,10 @@
 import android.content.pm.ActivityInfo;
 import android.graphics.PointF;
 import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.IVirtualDisplayCallback;
+import android.hardware.display.VirtualDisplayConfig;
 import android.hardware.input.VirtualDpadConfig;
 import android.hardware.input.VirtualKeyEvent;
 import android.hardware.input.VirtualKeyboardConfig;
@@ -82,6 +86,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.BlockedAppStreamingActivity;
+import com.android.server.LocalServices;
 import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener;
 import com.android.server.companion.virtual.audio.VirtualAudioController;
 
@@ -109,21 +114,29 @@
 
     private final Context mContext;
     private final AssociationInfo mAssociationInfo;
+    private final VirtualDeviceManagerService mService;
     private final PendingTrampolineCallback mPendingTrampolineCallback;
     private final int mOwnerUid;
     private final int mDeviceId;
+    // Thou shall not hold the mVirtualDeviceLock over the mInputController calls.
+    // Holding the lock can lead to lock inversion with GlobalWindowManagerLock.
+    // 1. After display is created the window manager calls into VDM during construction
+    //   of display specific context to fetch device id corresponding to the display.
+    //   mVirtualDeviceLock will be held while this is done.
+    // 2. InputController interactions result in calls to DisplayManager (to set IME,
+    //    possibly more indirect calls), and those attempt to lock GlobalWindowManagerLock which
+    //    creates lock inversion.
     private final InputController mInputController;
     private final SensorController mSensorController;
     private final CameraAccessController mCameraAccessController;
     private VirtualAudioController mVirtualAudioController;
-    @VisibleForTesting
-    final ArraySet<Integer> mVirtualDisplayIds = new ArraySet<>();
-    private final OnDeviceCloseListener mOnDeviceCloseListener;
     private final IBinder mAppToken;
     private final VirtualDeviceParams mParams;
-    private final Map<Integer, PowerManager.WakeLock> mPerDisplayWakelocks = new ArrayMap<>();
+    @GuardedBy("mVirtualDeviceLock")
+    private final SparseArray<VirtualDisplayWrapper> mVirtualDisplays = new SparseArray<>();
     private final IVirtualDeviceActivityListener mActivityListener;
     private final IVirtualDeviceSoundEffectListener mSoundEffectListener;
+    private final DisplayManagerGlobal mDisplayManager;
     @GuardedBy("mVirtualDeviceLock")
     private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>();
     @NonNull
@@ -174,21 +187,14 @@
         };
     }
 
-    /**
-     * A mapping from the virtual display ID to its corresponding
-     * {@link GenericWindowPolicyController}.
-     */
-    private final SparseArray<GenericWindowPolicyController> mWindowPolicyControllers =
-            new SparseArray<>();
-
     VirtualDeviceImpl(
             Context context,
             AssociationInfo associationInfo,
+            VirtualDeviceManagerService service,
             IBinder token,
             int ownerUid,
             int deviceId,
             CameraAccessController cameraAccessController,
-            OnDeviceCloseListener onDeviceCloseListener,
             PendingTrampolineCallback pendingTrampolineCallback,
             IVirtualDeviceActivityListener activityListener,
             IVirtualDeviceSoundEffectListener soundEffectListener,
@@ -197,40 +203,43 @@
         this(
                 context,
                 associationInfo,
+                service,
                 token,
                 ownerUid,
                 deviceId,
                 /* inputController= */ null,
                 /* sensorController= */ null,
                 cameraAccessController,
-                onDeviceCloseListener,
                 pendingTrampolineCallback,
                 activityListener,
                 soundEffectListener,
                 runningAppsChangedCallback,
-                params);
+                params,
+                DisplayManagerGlobal.getInstance());
     }
 
     @VisibleForTesting
     VirtualDeviceImpl(
             Context context,
             AssociationInfo associationInfo,
+            VirtualDeviceManagerService service,
             IBinder token,
             int ownerUid,
             int deviceId,
             InputController inputController,
             SensorController sensorController,
             CameraAccessController cameraAccessController,
-            OnDeviceCloseListener onDeviceCloseListener,
             PendingTrampolineCallback pendingTrampolineCallback,
             IVirtualDeviceActivityListener activityListener,
             IVirtualDeviceSoundEffectListener soundEffectListener,
             Consumer<ArraySet<Integer>> runningAppsChangedCallback,
-            VirtualDeviceParams params) {
+            VirtualDeviceParams params,
+            DisplayManagerGlobal displayManager) {
         super(PermissionEnforcer.fromContext(context));
         UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(ownerUid);
         mContext = context.createContextAsUser(ownerUserHandle, 0);
         mAssociationInfo = associationInfo;
+        mService = service;
         mPendingTrampolineCallback = pendingTrampolineCallback;
         mActivityListener = activityListener;
         mSoundEffectListener = soundEffectListener;
@@ -239,6 +248,7 @@
         mDeviceId = deviceId;
         mAppToken = token;
         mParams = params;
+        mDisplayManager = displayManager;
         if (inputController == null) {
             mInputController = new InputController(
                     mVirtualDeviceLock,
@@ -259,7 +269,6 @@
         }
         mCameraAccessController = cameraAccessController;
         mCameraAccessController.startObservingIfNeeded();
-        mOnDeviceCloseListener = onDeviceCloseListener;
         try {
             token.linkToDeath(this, 0);
         } catch (RemoteException e) {
@@ -331,9 +340,11 @@
     @Override // Binder call
     public void launchPendingIntent(int displayId, PendingIntent pendingIntent,
             ResultReceiver resultReceiver) {
-        if (!mVirtualDisplayIds.contains(displayId)) {
-            throw new SecurityException("Display ID " + displayId
-                    + " not found for this virtual device");
+        synchronized (mVirtualDeviceLock) {
+            if (!mVirtualDisplays.contains(displayId)) {
+                throw new SecurityException("Display ID " + displayId
+                        + " not found for this virtual device");
+            }
         }
         if (pendingIntent.isActivity()) {
             try {
@@ -383,24 +394,34 @@
     @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void close() {
         super.close_enforcePermission();
+        // Remove about-to-be-closed virtual device from the service before butchering it.
+        mService.removeVirtualDevice(mDeviceId);
+
+        VirtualDisplayWrapper[] virtualDisplaysToBeReleased;
         synchronized (mVirtualDeviceLock) {
-            if (!mPerDisplayWakelocks.isEmpty()) {
-                mPerDisplayWakelocks.forEach((displayId, wakeLock) -> {
-                    Slog.w(TAG, "VirtualDisplay " + displayId + " owned by UID " + mOwnerUid
-                            + " was not properly released");
-                    wakeLock.release();
-                });
-                mPerDisplayWakelocks.clear();
-            }
             if (mVirtualAudioController != null) {
                 mVirtualAudioController.stopListening();
                 mVirtualAudioController = null;
             }
             mLocaleList = null;
+            virtualDisplaysToBeReleased = new VirtualDisplayWrapper[mVirtualDisplays.size()];
+            for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                virtualDisplaysToBeReleased[i] = mVirtualDisplays.valueAt(i);
+            }
+            mVirtualDisplays.clear();
             mVirtualSensorList = null;
             mVirtualSensors.clear();
         }
-        mOnDeviceCloseListener.onClose(mDeviceId);
+        // Destroy the display outside locked section.
+        for (VirtualDisplayWrapper virtualDisplayWrapper : virtualDisplaysToBeReleased) {
+            mDisplayManager.releaseVirtualDisplay(virtualDisplayWrapper.getToken());
+            // The releaseVirtualDisplay call above won't trigger
+            // VirtualDeviceImpl.onVirtualDisplayRemoved callback because we already removed the
+            // virtual device from the service - we release the other display-tied resources here
+            // with the guarantee it will be done exactly once.
+            releaseOwnedVirtualDisplayResources(virtualDisplayWrapper);
+        }
+
         mAppToken.unlinkToDeath(this, 0);
         mCameraAccessController.stopObservingIfNeeded();
 
@@ -429,11 +450,6 @@
         return mVirtualAudioController;
     }
 
-    @VisibleForTesting
-    SparseArray<GenericWindowPolicyController> getWindowPolicyControllersForTesting() {
-        return mWindowPolicyControllers;
-    }
-
     @Override // Binder call
     @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void onAudioSessionStarting(int displayId,
@@ -441,7 +457,7 @@
             @Nullable IAudioConfigChangedCallback configChangedCallback) {
         super.onAudioSessionStarting_enforcePermission();
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(displayId)) {
+            if (!mVirtualDisplays.contains(displayId)) {
                 throw new SecurityException(
                         "Cannot start audio session for a display not associated with this virtual "
                                 + "device");
@@ -449,7 +465,8 @@
 
             if (mVirtualAudioController == null) {
                 mVirtualAudioController = new VirtualAudioController(mContext);
-                GenericWindowPolicyController gwpc = mWindowPolicyControllers.get(displayId);
+                GenericWindowPolicyController gwpc = mVirtualDisplays.get(
+                        displayId).getWindowPolicyController();
                 mVirtualAudioController.startListening(gwpc, routingCallback,
                         configChangedCallback);
             }
@@ -473,7 +490,7 @@
     public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) {
         super.createVirtualDpad_enforcePermission();
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+            if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
                         "Cannot create a virtual dpad for a display not associated with "
                                 + "this virtual device");
@@ -493,7 +510,7 @@
     public void createVirtualKeyboard(VirtualKeyboardConfig config, @NonNull IBinder deviceToken) {
         super.createVirtualKeyboard_enforcePermission();
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+            if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
                         "Cannot create a virtual keyboard for a display not associated with "
                                 + "this virtual device");
@@ -515,7 +532,7 @@
     public void createVirtualMouse(VirtualMouseConfig config, @NonNull IBinder deviceToken) {
         super.createVirtualMouse_enforcePermission();
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+            if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
                         "Cannot create a virtual mouse for a display not associated with this "
                                 + "virtual device");
@@ -536,7 +553,7 @@
             @NonNull IBinder deviceToken) {
         super.createVirtualTouchscreen_enforcePermission();
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+            if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
                         "Cannot create a virtual touchscreen for a display not associated with "
                                 + "this virtual device");
@@ -566,7 +583,7 @@
             @NonNull IBinder deviceToken) {
         super.createVirtualNavigationTouchpad_enforcePermission();
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+            if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
                         "Cannot create a virtual navigation touchpad for a display not associated "
                                 + "with this virtual device");
@@ -704,7 +721,8 @@
         try {
             synchronized (mVirtualDeviceLock) {
                 mDefaultShowPointerIcon = showPointerIcon;
-                for (int displayId : mVirtualDisplayIds) {
+                for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                    final int displayId = mVirtualDisplays.keyAt(i);
                     mInputController.setShowPointerIcon(mDefaultShowPointerIcon, displayId);
                 }
             }
@@ -795,8 +813,8 @@
         fout.println("    mParams: " + mParams);
         fout.println("    mVirtualDisplayIds: ");
         synchronized (mVirtualDeviceLock) {
-            for (int id : mVirtualDisplayIds) {
-                fout.println("      " + id);
+            for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                fout.println("      " + mVirtualDisplays.keyAt(i));
             }
             fout.println("    mDefaultShowPointerIcon: " + mDefaultShowPointerIcon);
         }
@@ -804,61 +822,75 @@
         mSensorController.dump(fout);
     }
 
-    GenericWindowPolicyController createWindowPolicyController(
+    private GenericWindowPolicyController createWindowPolicyController(
             @NonNull List<String> displayCategories) {
-        synchronized (mVirtualDeviceLock) {
-            final GenericWindowPolicyController gwpc =
-                    new GenericWindowPolicyController(FLAG_SECURE,
-                            SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
-                            getAllowedUserHandles(),
-                            mParams.getAllowedCrossTaskNavigations(),
-                            mParams.getBlockedCrossTaskNavigations(),
-                            mParams.getAllowedActivities(),
-                            mParams.getBlockedActivities(),
-                            mParams.getDefaultActivityPolicy(),
-                            createListenerAdapter(),
-                            this::onEnteringPipBlocked,
-                            this::onActivityBlocked,
-                            this::onSecureWindowShown,
-                            this::shouldInterceptIntent,
-                            displayCategories,
-                            mParams.getDefaultRecentsPolicy());
-            gwpc.registerRunningAppsChangedListener(/* listener= */ this);
-            return gwpc;
-        }
+        final GenericWindowPolicyController gwpc =
+                new GenericWindowPolicyController(FLAG_SECURE,
+                        SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+                        getAllowedUserHandles(),
+                        mParams.getAllowedCrossTaskNavigations(),
+                        mParams.getBlockedCrossTaskNavigations(),
+                        mParams.getAllowedActivities(),
+                        mParams.getBlockedActivities(),
+                        mParams.getDefaultActivityPolicy(),
+                        createListenerAdapter(),
+                        this::onEnteringPipBlocked,
+                        this::onActivityBlocked,
+                        this::onSecureWindowShown,
+                        this::shouldInterceptIntent,
+                        displayCategories,
+                        mParams.getDefaultRecentsPolicy());
+        gwpc.registerRunningAppsChangedListener(/* listener= */ this);
+        return gwpc;
     }
 
-    void onVirtualDisplayCreatedLocked(GenericWindowPolicyController gwpc, int displayId) {
+    int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
+            @NonNull IVirtualDisplayCallback callback, String packageName) {
+        GenericWindowPolicyController gwpc = createWindowPolicyController(
+                virtualDisplayConfig.getDisplayCategories());
+        DisplayManagerInternal displayManager = LocalServices.getService(
+                DisplayManagerInternal.class);
+        int displayId;
+        displayId = displayManager.createVirtualDisplay(virtualDisplayConfig, callback,
+                this, gwpc, packageName);
+        gwpc.setDisplayId(displayId);
+
         synchronized (mVirtualDeviceLock) {
-            if (displayId == Display.INVALID_DISPLAY) {
-                return;
-            }
-            if (mVirtualDisplayIds.contains(displayId)) {
+            if (mVirtualDisplays.contains(displayId)) {
+                gwpc.unregisterRunningAppsChangedListener(this);
                 throw new IllegalStateException(
                         "Virtual device already has a virtual display with ID " + displayId);
             }
-            mVirtualDisplayIds.add(displayId);
 
-            gwpc.setDisplayId(displayId);
-            mWindowPolicyControllers.put(displayId, gwpc);
+            PowerManager.WakeLock wakeLock = createAndAcquireWakeLockForDisplay(displayId);
+            mVirtualDisplays.put(displayId, new VirtualDisplayWrapper(callback, gwpc, wakeLock));
+        }
 
+        final long token = Binder.clearCallingIdentity();
+        try {
             mInputController.setShowPointerIcon(mDefaultShowPointerIcon, displayId);
             mInputController.setPointerAcceleration(1f, displayId);
             mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false,
                     displayId);
             mInputController.setLocalIme(displayId);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
 
+        return displayId;
+    }
 
-            if (mPerDisplayWakelocks.containsKey(displayId)) {
-                Slog.e(TAG, "Not creating wakelock for displayId " + displayId);
-                return;
-            }
+    private PowerManager.WakeLock createAndAcquireWakeLockForDisplay(int displayId) {
+        final long token = Binder.clearCallingIdentity();
+        try {
             PowerManager powerManager = mContext.getSystemService(PowerManager.class);
             PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
                     PowerManager.SCREEN_BRIGHT_WAKE_LOCK,
                     TAG + ":" + displayId, displayId);
-            mPerDisplayWakelocks.put(displayId, wakeLock);
             wakeLock.acquire();
+            return wakeLock;
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
     }
 
@@ -872,8 +904,10 @@
     }
 
     private void onSecureWindowShown(int displayId, int uid) {
-        if (!mVirtualDisplayIds.contains(displayId)) {
-            return;
+        synchronized (mVirtualDeviceLock) {
+            if (!mVirtualDisplays.contains(displayId)) {
+                return;
+            }
         }
 
         // If a virtual display isn't secure, the screen can't be captured. Show a warning toast
@@ -888,55 +922,102 @@
 
     private ArraySet<UserHandle> getAllowedUserHandles() {
         ArraySet<UserHandle> result = new ArraySet<>();
-        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-        UserManager userManager = mContext.getSystemService(UserManager.class);
-        for (UserHandle profile : userManager.getAllProfiles()) {
-            int nearbyAppStreamingPolicy = dpm.getNearbyAppStreamingPolicy(profile.getIdentifier());
-            if (nearbyAppStreamingPolicy == NEARBY_STREAMING_ENABLED
-                    || nearbyAppStreamingPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY) {
-                result.add(profile);
-            } else if (nearbyAppStreamingPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY) {
-                if (mParams.getUsersWithMatchingAccounts().contains(profile)) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+            UserManager userManager = mContext.getSystemService(UserManager.class);
+            for (UserHandle profile : userManager.getAllProfiles()) {
+                int nearbyAppStreamingPolicy = dpm.getNearbyAppStreamingPolicy(
+                        profile.getIdentifier());
+                if (nearbyAppStreamingPolicy == NEARBY_STREAMING_ENABLED
+                        || nearbyAppStreamingPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY) {
                     result.add(profile);
+                } else if (nearbyAppStreamingPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY) {
+                    if (mParams.getUsersWithMatchingAccounts().contains(profile)) {
+                        result.add(profile);
+                    }
                 }
             }
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
         return result;
     }
 
-    void onVirtualDisplayRemovedLocked(int displayId) {
+
+    void onVirtualDisplayRemoved(int displayId) {
+        /* This is callback invoked by VirtualDeviceManagerService when VirtualDisplay was released
+         * by DisplayManager (most probably caused by someone calling VirtualDisplay.close()).
+         * At this point, the display is already released, but we still need to release the
+         * corresponding wakeLock and unregister the RunningAppsChangedListener from corresponding
+         * WindowPolicyController.
+         *
+         * Note that when the display is destroyed during VirtualDeviceImpl.close() call,
+         * this callback won't be invoked because the display is removed from
+         * VirtualDeviceManagerService before any resources are released.
+         */
+        VirtualDisplayWrapper virtualDisplayWrapper;
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(displayId)) {
-                throw new IllegalStateException(
-                        "Virtual device doesn't have a virtual display with ID " + displayId);
-            }
-            PowerManager.WakeLock wakeLock = mPerDisplayWakelocks.get(displayId);
-            if (wakeLock != null) {
-                wakeLock.release();
-                mPerDisplayWakelocks.remove(displayId);
-            }
-            GenericWindowPolicyController gwpc = mWindowPolicyControllers.get(displayId);
-            if (gwpc != null) {
-                gwpc.unregisterRunningAppsChangedListener(/* listener= */ this);
-            }
-            mVirtualDisplayIds.remove(displayId);
-            mWindowPolicyControllers.remove(displayId);
+            virtualDisplayWrapper = mVirtualDisplays.removeReturnOld(displayId);
         }
+
+        if (virtualDisplayWrapper == null) {
+            throw new IllegalStateException(
+                    "Virtual device doesn't have a virtual display with ID " + displayId);
+        }
+
+        releaseOwnedVirtualDisplayResources(virtualDisplayWrapper);
+
+    }
+
+    /**
+     * Release resources tied to virtual display owned by this VirtualDevice instance.
+     *
+     * Note that this method won't release the virtual display itself.
+     *
+     * @param virtualDisplayWrapper - VirtualDisplayWrapper to release resources for.
+     */
+    private void releaseOwnedVirtualDisplayResources(VirtualDisplayWrapper virtualDisplayWrapper) {
+        virtualDisplayWrapper.getWakeLock().release();
+        virtualDisplayWrapper.getWindowPolicyController().unregisterRunningAppsChangedListener(
+                this);
     }
 
     int getOwnerUid() {
         return mOwnerUid;
     }
 
+    ArraySet<Integer> getDisplayIds() {
+        synchronized (mVirtualDeviceLock) {
+            final int size = mVirtualDisplays.size();
+            ArraySet<Integer> arraySet = new ArraySet<>(size);
+            for (int i = 0; i < size; i++) {
+                arraySet.append(mVirtualDisplays.keyAt(i));
+            }
+            return arraySet;
+        }
+    }
+
+    @VisibleForTesting
+    GenericWindowPolicyController getDisplayWindowPolicyControllerForTest(int displayId) {
+        VirtualDisplayWrapper virtualDisplayWrapper;
+        synchronized (mVirtualDeviceLock) {
+            virtualDisplayWrapper = mVirtualDisplays.get(displayId);
+        }
+        return virtualDisplayWrapper != null ? virtualDisplayWrapper.getWindowPolicyController()
+                : null;
+    }
+
     /**
      * Returns true if an app with the given {@code uid} is currently running on this virtual
      * device.
      */
     boolean isAppRunningOnVirtualDevice(int uid) {
-        final int size = mWindowPolicyControllers.size();
-        for (int i = 0; i < size; i++) {
-            if (mWindowPolicyControllers.valueAt(i).containsUid(uid)) {
-                return true;
+        synchronized (mVirtualDeviceLock) {
+            for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                if (mVirtualDisplays.valueAt(i).getWindowPolicyController().containsUid(uid)) {
+                    return true;
+                }
             }
         }
         return false;
@@ -957,11 +1038,9 @@
             Looper looper) {
         synchronized (mVirtualDeviceLock) {
             DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
-            final int size = mWindowPolicyControllers.size();
-            for (int i = 0; i < size; i++) {
-                if (mWindowPolicyControllers.valueAt(i).containsUid(uid)) {
-                    int displayId = mWindowPolicyControllers.keyAt(i);
-                    Display display = displayManager.getDisplay(displayId);
+            for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                if (mVirtualDisplays.valueAt(i).getWindowPolicyController().containsUid(uid)) {
+                    Display display = displayManager.getDisplay(mVirtualDisplays.keyAt(i));
                     if (display != null && display.isValid()) {
                         Toast.makeText(mContext.createDisplayContext(display), looper, text,
                                 duration).show();
@@ -972,7 +1051,9 @@
     }
 
     boolean isDisplayOwnedByVirtualDevice(int displayId) {
-        return mVirtualDisplayIds.contains(displayId);
+        synchronized (mVirtualDeviceLock) {
+            return mVirtualDisplays.contains(displayId);
+        }
     }
 
     void onEnteringPipBlocked(int uid) {
@@ -1016,10 +1097,6 @@
         }
     }
 
-    interface OnDeviceCloseListener {
-        void onClose(int deviceId);
-    }
-
     interface PendingTrampolineCallback {
         /**
          * Called when the callback should start waiting for the given pending trampoline.
@@ -1073,4 +1150,31 @@
                     + ", displayId=" + mDisplayId + "}";
         }
     }
+
+    /** Data class wrapping resources tied to single virtual display. */
+    private static final class VirtualDisplayWrapper {
+        private final IVirtualDisplayCallback mToken;
+        private final GenericWindowPolicyController mWindowPolicyController;
+        private final PowerManager.WakeLock mWakeLock;
+
+        VirtualDisplayWrapper(@NonNull IVirtualDisplayCallback token,
+                @NonNull GenericWindowPolicyController windowPolicyController,
+                @NonNull PowerManager.WakeLock wakeLock) {
+            mToken = Objects.requireNonNull(token);
+            mWindowPolicyController = Objects.requireNonNull(windowPolicyController);
+            mWakeLock = Objects.requireNonNull(wakeLock);
+        }
+
+        GenericWindowPolicyController getWindowPolicyController() {
+            return mWindowPolicyController;
+        }
+
+        PowerManager.WakeLock getWakeLock() {
+            return mWakeLock;
+        }
+
+        IVirtualDisplayCallback getToken() {
+            return mToken;
+        }
+    }
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 9bb05a6..ed5b858 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -37,7 +37,6 @@
 import android.companion.virtual.sensor.VirtualSensor;
 import android.content.Context;
 import android.content.Intent;
-import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.IVirtualDisplayCallback;
 import android.hardware.display.VirtualDisplayConfig;
 import android.os.Binder;
@@ -108,26 +107,26 @@
     private final ActivityInterceptorCallback mActivityInterceptorCallback =
             new ActivityInterceptorCallback() {
 
-        @Nullable
-        @Override
-        public ActivityInterceptResult onInterceptActivityLaunch(@NonNull
-                ActivityInterceptorInfo info) {
-            if (info.getCallingPackage() == null) {
-                return null;
-            }
-            PendingTrampoline pt = mPendingTrampolines.remove(info.getCallingPackage());
-            if (pt == null) {
-                return null;
-            }
-            pt.mResultReceiver.send(VirtualDeviceManager.LAUNCH_SUCCESS, null);
-            ActivityOptions options = info.getCheckedOptions();
-            if (options == null) {
-                options = ActivityOptions.makeBasic();
-            }
-            return new ActivityInterceptResult(
-                    info.getIntent(), options.setLaunchDisplayId(pt.mDisplayId));
-        }
-    };
+                @Nullable
+                @Override
+                public ActivityInterceptResult onInterceptActivityLaunch(@NonNull
+                        ActivityInterceptorInfo info) {
+                    if (info.getCallingPackage() == null) {
+                        return null;
+                    }
+                    PendingTrampoline pt = mPendingTrampolines.remove(info.getCallingPackage());
+                    if (pt == null) {
+                        return null;
+                    }
+                    pt.mResultReceiver.send(VirtualDeviceManager.LAUNCH_SUCCESS, null);
+                    ActivityOptions options = info.getCheckedOptions();
+                    if (options == null) {
+                        options = ActivityOptions.makeBasic();
+                    }
+                    return new ActivityInterceptResult(
+                            info.getIntent(), options.setLaunchDisplayId(pt.mDisplayId));
+                }
+            };
 
     @Override
     public void onStart() {
@@ -146,8 +145,8 @@
                 CharSequence deviceName = mVirtualDevices.valueAt(i).getDisplayName();
                 mVirtualDevices.valueAt(i).showToastWhereUidIsRunning(appUid,
                         getContext().getString(
-                            com.android.internal.R.string.vdm_camera_access_denied,
-                            deviceName),
+                                com.android.internal.R.string.vdm_camera_access_denied,
+                                deviceName),
                         Toast.LENGTH_LONG, Looper.myLooper());
             }
         }
@@ -193,34 +192,46 @@
         }
     }
 
-    @VisibleForTesting
     void removeVirtualDevice(int deviceId) {
         synchronized (mVirtualDeviceManagerLock) {
             mAppsOnVirtualDevices.remove(deviceId);
             mVirtualDevices.remove(deviceId);
         }
+
+        Intent i = new Intent(VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
+        i.putExtra(VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, deviceId);
+        i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            getContext().sendBroadcastAsUser(i, UserHandle.ALL);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub {
 
         private final VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback =
                 new VirtualDeviceImpl.PendingTrampolineCallback() {
-            @Override
-            public void startWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
-                PendingTrampoline existing = mPendingTrampolines.put(
-                        pendingTrampoline.mPendingIntent.getCreatorPackage(),
-                        pendingTrampoline);
-                if (existing != null) {
-                    existing.mResultReceiver.send(
-                            VirtualDeviceManager.LAUNCH_FAILURE_NO_ACTIVITY, null);
-                }
-            }
+                    @Override
+                    public void startWaitingForPendingTrampoline(
+                            PendingTrampoline pendingTrampoline) {
+                        PendingTrampoline existing = mPendingTrampolines.put(
+                                pendingTrampoline.mPendingIntent.getCreatorPackage(),
+                                pendingTrampoline);
+                        if (existing != null) {
+                            existing.mResultReceiver.send(
+                                    VirtualDeviceManager.LAUNCH_FAILURE_NO_ACTIVITY, null);
+                        }
+                    }
 
-            @Override
-            public void stopWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
-                mPendingTrampolines.remove(pendingTrampoline.mPendingIntent.getCreatorPackage());
-            }
-        };
+                    @Override
+                    public void stopWaitingForPendingTrampoline(
+                            PendingTrampoline pendingTrampoline) {
+                        mPendingTrampolines.remove(
+                                pendingTrampoline.mPendingIntent.getCreatorPackage());
+                    }
+                };
 
         @Override // Binder call
         public IVirtualDevice createVirtualDevice(
@@ -251,8 +262,9 @@
                 final Consumer<ArraySet<Integer>> runningAppsChangedCallback =
                         runningUids -> notifyRunningAppsChanged(deviceId, runningUids);
                 VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
-                        associationInfo, token, callingUid, deviceId, cameraAccessController,
-                        this::onDeviceClosed, mPendingTrampolineCallback, activityListener,
+                        associationInfo, VirtualDeviceManagerService.this, token, callingUid,
+                        deviceId, cameraAccessController,
+                        mPendingTrampolineCallback, activityListener,
                         soundEffectListener, runningAppsChangedCallback, params);
                 mVirtualDevices.put(deviceId, virtualDevice);
                 return virtualDevice;
@@ -281,26 +293,9 @@
                         "uid " + callingUid
                                 + " is not the owner of the supplied VirtualDevice");
             }
-            GenericWindowPolicyController gwpc;
-            final long token = Binder.clearCallingIdentity();
-            try {
-                gwpc = virtualDeviceImpl.createWindowPolicyController(
-                    virtualDisplayConfig.getDisplayCategories());
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
 
-            DisplayManagerInternal displayManager = getLocalService(
-                    DisplayManagerInternal.class);
-            int displayId = displayManager.createVirtualDisplay(virtualDisplayConfig, callback,
-                    virtualDevice, gwpc, packageName);
-
-            final long tokenTwo = Binder.clearCallingIdentity();
-            try {
-                virtualDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, displayId);
-            } finally {
-                Binder.restoreCallingIdentity(tokenTwo);
-            }
+            int displayId = virtualDeviceImpl.createVirtualDisplay(virtualDisplayConfig, callback,
+                    packageName);
             mLocalService.onVirtualDisplayCreated(displayId);
             return displayId;
         }
@@ -412,19 +407,6 @@
             return null;
         }
 
-        private void onDeviceClosed(int deviceId) {
-            removeVirtualDevice(deviceId);
-            Intent i = new Intent(VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
-            i.putExtra(VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, deviceId);
-            i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                getContext().sendBroadcastAsUser(i, UserHandle.ALL);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-
         @Override
         public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                 throws RemoteException {
@@ -512,9 +494,14 @@
         @Override
         public void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId) {
             final VirtualDisplayListener[] listeners;
+            VirtualDeviceImpl virtualDeviceImpl;
             synchronized (mVirtualDeviceManagerLock) {
-                ((VirtualDeviceImpl) virtualDevice).onVirtualDisplayRemovedLocked(displayId);
                 listeners = mVirtualDisplayListeners.toArray(new VirtualDisplayListener[0]);
+                virtualDeviceImpl = mVirtualDevices.get(
+                        ((VirtualDeviceImpl) virtualDevice).getDeviceId());
+            }
+            if (virtualDeviceImpl != null) {
+                virtualDeviceImpl.onVirtualDisplayRemoved(displayId);
             }
             mHandler.post(() -> {
                 for (VirtualDisplayListener listener : listeners) {
@@ -599,16 +586,11 @@
 
         @Override
         public @NonNull ArraySet<Integer> getDisplayIdsForDevice(int deviceId) {
+            VirtualDeviceImpl virtualDevice;
             synchronized (mVirtualDeviceManagerLock) {
-                int size = mVirtualDevices.size();
-                for (int i = 0; i < size; i++) {
-                    VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
-                    if (device.getDeviceId() == deviceId) {
-                        return new ArraySet<>(device.mVirtualDisplayIds);
-                    }
-                }
+                virtualDevice = mVirtualDevices.get(deviceId);
             }
-            return new ArraySet<>();
+            return virtualDevice == null ? new ArraySet<>() : virtualDevice.getDisplayIds();
         }
 
         @Override
diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
index da65f27..2efb0be 100644
--- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -24,13 +24,14 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.util.Log;
 import android.view.inputmethod.ImeTracker;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.IImeTracker;
 import com.android.internal.inputmethod.InputMethodDebug;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.view.IImeTracker;
 
 import java.io.PrintWriter;
 import java.time.Instant;
@@ -53,7 +54,7 @@
 @SuppressWarnings("GuardedBy")
 public final class ImeTrackerService extends IImeTracker.Stub {
 
-    static final String TAG = "ImeTrackerService";
+    private static final String TAG = ImeTracker.TAG;
 
     /** The threshold in milliseconds after which a history entry is considered timed out. */
     private static final long TIMEOUT_MS = 10_000;
@@ -71,67 +72,71 @@
 
     @NonNull
     @Override
-    public synchronized IBinder onRequestShow(int uid, @ImeTracker.Origin int origin,
-            @SoftInputShowHideReason int reason) {
-        final IBinder binder = new Binder();
-        final History.Entry entry = new History.Entry(uid, ImeTracker.TYPE_SHOW,
-                ImeTracker.STATUS_RUN, origin, reason);
+    public synchronized ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
+            @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+        final var binder = new Binder();
+        final var token = new ImeTracker.Token(binder, tag);
+        final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_SHOW, ImeTracker.STATUS_RUN,
+                origin, reason);
         mHistory.addEntry(binder, entry);
 
         // Register a delayed task to handle the case where the new entry times out.
         mHandler.postDelayed(() -> {
             synchronized (ImeTrackerService.this) {
-                mHistory.setFinished(binder, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
+                mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
             }
         }, TIMEOUT_MS);
 
-        return binder;
+        return token;
     }
 
     @NonNull
     @Override
-    public synchronized IBinder onRequestHide(int uid, @ImeTracker.Origin int origin,
-            @SoftInputShowHideReason int reason) {
-        final IBinder binder = new Binder();
-        final History.Entry entry = new History.Entry(uid, ImeTracker.TYPE_HIDE,
-                ImeTracker.STATUS_RUN, origin, reason);
+    public synchronized ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
+            @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+        final var binder = new Binder();
+        final var token = new ImeTracker.Token(binder, tag);
+        final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_HIDE, ImeTracker.STATUS_RUN,
+                origin, reason);
         mHistory.addEntry(binder, entry);
 
         // Register a delayed task to handle the case where the new entry times out.
         mHandler.postDelayed(() -> {
             synchronized (ImeTrackerService.this) {
-                mHistory.setFinished(binder, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
+                mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
             }
         }, TIMEOUT_MS);
 
-        return binder;
+        return token;
     }
 
     @Override
-    public synchronized void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
-        final History.Entry entry = mHistory.getEntry(statsToken);
+    public synchronized void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) {
+        final var entry = mHistory.getEntry(binder);
         if (entry == null) return;
 
         entry.mPhase = phase;
     }
 
     @Override
-    public synchronized void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+    public synchronized void onFailed(@NonNull ImeTracker.Token statsToken,
+            @ImeTracker.Phase int phase) {
         mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase);
     }
 
     @Override
-    public synchronized void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+    public synchronized void onCancelled(@NonNull ImeTracker.Token statsToken,
+            @ImeTracker.Phase int phase) {
         mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase);
     }
 
     @Override
-    public synchronized void onShown(@NonNull IBinder statsToken) {
+    public synchronized void onShown(@NonNull ImeTracker.Token statsToken) {
         mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
     }
 
     @Override
-    public synchronized void onHidden(@NonNull IBinder statsToken) {
+    public synchronized void onHidden(@NonNull ImeTracker.Token statsToken) {
         mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
     }
 
@@ -141,9 +146,9 @@
      * @param statsToken the token corresponding to the current IME request.
      * @param requestWindowName the name of the window that created the IME request.
      */
-    public synchronized void onImmsUpdate(@NonNull IBinder statsToken,
+    public synchronized void onImmsUpdate(@NonNull ImeTracker.Token statsToken,
             @NonNull String requestWindowName) {
-        final History.Entry entry = mHistory.getEntry(statsToken);
+        final var entry = mHistory.getEntry(statsToken.getBinder());
         if (entry == null) return;
 
         entry.mRequestWindowName = requestWindowName;
@@ -181,17 +186,17 @@
         /** Latest entry sequence number. */
         private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
 
-        /** Adds a live entry. */
+        /** Adds a live entry corresponding to the given IME tracking token's binder. */
         @GuardedBy("ImeTrackerService.this")
-        private void addEntry(@NonNull IBinder statsToken, @NonNull Entry entry) {
-            mLiveEntries.put(statsToken, entry);
+        private void addEntry(@NonNull IBinder binder, @NonNull Entry entry) {
+            mLiveEntries.put(binder, entry);
         }
 
-        /** Gets the entry corresponding to the given IME tracking token, if it exists. */
+        /** Gets the entry corresponding to the given IME tracking token's binder, if it exists. */
         @Nullable
         @GuardedBy("ImeTrackerService.this")
-        private Entry getEntry(@NonNull IBinder statsToken) {
-            return mLiveEntries.get(statsToken);
+        private Entry getEntry(@NonNull IBinder binder) {
+            return mLiveEntries.get(binder);
         }
 
         /**
@@ -204,10 +209,21 @@
          *              (or {@link ImeTracker#PHASE_NOT_SET} otherwise).
          */
         @GuardedBy("ImeTrackerService.this")
-        private void setFinished(@NonNull IBinder statsToken, @ImeTracker.Status int status,
-                @ImeTracker.Phase int phase) {
-            final Entry entry = mLiveEntries.remove(statsToken);
-            if (entry == null) return;
+        private void setFinished(@NonNull ImeTracker.Token statsToken,
+                @ImeTracker.Status int status, @ImeTracker.Phase int phase) {
+            final var entry = mLiveEntries.remove(statsToken.getBinder());
+            if (entry == null) {
+                // This will be unconditionally called through the postDelayed above to handle
+                // potential timeouts, and is thus intentionally dropped to avoid having to manually
+                // save and remove the registered callback. Only timeout calls are expected.
+                if (status != ImeTracker.STATUS_TIMEOUT) {
+                    Log.i(TAG, statsToken.getTag()
+                            + ": setFinished on previously finished token at "
+                            + ImeTracker.Debug.phaseToString(phase) + " with "
+                            + ImeTracker.Debug.statusToString(status));
+                }
+                return;
+            }
 
             entry.mDuration = System.currentTimeMillis() - entry.mStartTime;
             entry.mStatus = status;
@@ -216,6 +232,13 @@
                 entry.mPhase = phase;
             }
 
+            if (status == ImeTracker.STATUS_TIMEOUT) {
+                // All events other than timeouts are already logged in the client-side ImeTracker.
+                Log.i(TAG, statsToken.getTag() + ": setFinished at "
+                        + ImeTracker.Debug.phaseToString(entry.mPhase) + " with "
+                        + ImeTracker.Debug.statusToString(status));
+            }
+
             // Remove excess entries overflowing capacity (plus one for the new entry).
             while (mEntries.size() >= CAPACITY) {
                 mEntries.remove();
@@ -232,21 +255,22 @@
         /** Dumps the contents of the circular buffer. */
         @GuardedBy("ImeTrackerService.this")
         private void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
-            final DateTimeFormatter formatter =
-                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
-                            .withZone(ZoneId.systemDefault());
+            final var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+                    .withZone(ZoneId.systemDefault());
 
             pw.print(prefix);
-            pw.println("ImeTrackerService#History.mLiveEntries:");
+            pw.println("ImeTrackerService#History.mLiveEntries: "
+                    + mLiveEntries.size() + " elements");
 
-            for (final Entry entry: mLiveEntries.values()) {
+            for (final var entry: mLiveEntries.values()) {
                 dumpEntry(entry, pw, prefix, formatter);
             }
 
             pw.print(prefix);
-            pw.println("ImeTrackerService#History.mEntries:");
+            pw.println("ImeTrackerService#History.mEntries: "
+                    + mEntries.size() + " elements");
 
-            for (final Entry entry: mEntries) {
+            for (final var entry: mEntries) {
                 dumpEntry(entry, pw, prefix, formatter);
             }
         }
@@ -255,34 +279,22 @@
         private void dumpEntry(@NonNull Entry entry, @NonNull PrintWriter pw,
                 @NonNull String prefix, @NonNull DateTimeFormatter formatter) {
             pw.print(prefix);
-            pw.println("ImeTrackerService#History #" + entry.mSequenceNumber + ":");
+            pw.print(" #" + entry.mSequenceNumber);
+            pw.print(" " + ImeTracker.Debug.typeToString(entry.mType));
+            pw.print(" - " + ImeTracker.Debug.statusToString(entry.mStatus));
+            pw.print(" - " + entry.mTag);
+            pw.println(" (" + entry.mDuration + "ms):");
 
             pw.print(prefix);
-            pw.println(" startTime=" + formatter.format(Instant.ofEpochMilli(entry.mStartTime)));
+            pw.print("   startTime=" + formatter.format(Instant.ofEpochMilli(entry.mStartTime)));
+            pw.println(" " + ImeTracker.Debug.originToString(entry.mOrigin));
 
             pw.print(prefix);
-            pw.println(" duration=" + entry.mDuration + "ms");
+            pw.print("   reason=" + InputMethodDebug.softInputDisplayReasonToString(entry.mReason));
+            pw.println(" " + ImeTracker.Debug.phaseToString(entry.mPhase));
 
             pw.print(prefix);
-            pw.print(" type=" + ImeTracker.Debug.typeToString(entry.mType));
-
-            pw.print(prefix);
-            pw.print(" status=" + ImeTracker.Debug.statusToString(entry.mStatus));
-
-            pw.print(prefix);
-            pw.print(" origin="
-                    + ImeTracker.Debug.originToString(entry.mOrigin));
-
-            pw.print(prefix);
-            pw.print(" reason="
-                    + InputMethodDebug.softInputDisplayReasonToString(entry.mReason));
-
-            pw.print(prefix);
-            pw.print(" phase="
-                    + ImeTracker.Debug.phaseToString(entry.mPhase));
-
-            pw.print(prefix);
-            pw.print(" requestWindowName=" + entry.mRequestWindowName);
+            pw.println("   requestWindowName=" + entry.mRequestWindowName);
         }
 
         /** A history entry. */
@@ -291,6 +303,10 @@
             /** The entry's sequence number in the history. */
             private final int mSequenceNumber = sSequenceNumber.getAndIncrement();
 
+            /** Logging tag, of the shape "component:random_hexadecimal". */
+            @NonNull
+            private final String mTag;
+
             /** Uid of the client that requested the IME. */
             private final int mUid;
 
@@ -323,13 +339,15 @@
             /**
              * Name of the window that created the IME request.
              *
-             * Note: This is later set through {@link #onImmsUpdate(IBinder, String)}.
+             * Note: This is later set through {@link #onImmsUpdate}.
              */
             @NonNull
             private String mRequestWindowName = "not set";
 
-            private Entry(int uid, @ImeTracker.Type int type, @ImeTracker.Status int status,
-                    @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+            private Entry(@NonNull String tag, int uid, @ImeTracker.Type int type,
+                    @ImeTracker.Status int status, @ImeTracker.Origin int origin,
+                    @SoftInputShowHideReason int reason) {
+                mTag = tag;
                 mUid = uid;
                 mType = type;
                 mStatus = status;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 8ef4e4a..f142293 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -145,6 +145,7 @@
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.inputmethod.DirectBootAwareness;
 import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
+import com.android.internal.inputmethod.IImeTracker;
 import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
 import com.android.internal.inputmethod.IInputContentUriToken;
 import com.android.internal.inputmethod.IInputMethod;
@@ -168,7 +169,6 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.DumpUtils;
-import com.android.internal.view.IImeTracker;
 import com.android.internal.view.IInputMethodManager;
 import com.android.server.AccessibilityManagerInternal;
 import com.android.server.EventLogTags;
@@ -532,6 +532,7 @@
     /**
      * The client that is currently bound to an input method.
      */
+    @Nullable
     private ClientState mCurClient;
 
     /**
@@ -557,11 +558,26 @@
     int mCurFocusedWindowSoftInputMode;
 
     /**
-     * The client by which {@link #mCurFocusedWindow} was reported.
+     * The client by which {@link #mCurFocusedWindow} was reported. This gets updated whenever an
+     * IME-focusable window gained focus (without necessarily starting an input connection),
+     * while {@link #mCurClient} only gets updated when we actually start an input connection.
+     *
+     * @see #mCurFocusedWindow
      */
+    @Nullable
     ClientState mCurFocusedWindowClient;
 
     /**
+     * The editor info by which {@link #mCurFocusedWindow} was reported. This differs from
+     * {@link #mCurEditorInfo} the same way {@link #mCurFocusedWindowClient} differs
+     * from {@link #mCurClient}.
+     *
+     * @see #mCurFocusedWindow
+     */
+    @Nullable
+    EditorInfo mCurFocusedWindowEditorInfo;
+
+    /**
      * The {@link IRemoteInputConnection} last provided by the current client.
      */
     IRemoteInputConnection mCurInputConnection;
@@ -580,6 +596,7 @@
     /**
      * The {@link EditorInfo} last provided by the current client.
      */
+    @Nullable
     EditorInfo mCurEditorInfo;
 
     /**
@@ -2265,6 +2282,7 @@
                 }
                 if (mCurFocusedWindowClient == cs) {
                     mCurFocusedWindowClient = null;
+                    mCurFocusedWindowEditorInfo = null;
                 }
             }
         }
@@ -3453,10 +3471,7 @@
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         // Create statsToken is none exists.
         if (statsToken == null) {
-            // TODO(b/261565259): to avoid using null, add package name in ClientState
-            final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
-            final int uid = mCurClient != null ? mCurClient.mUid : -1;
-            statsToken = ImeTracker.forLogging().onRequestShow(packageName, uid,
+            statsToken = createStatsTokenForFocusedClient(true /* show */,
                     ImeTracker.ORIGIN_SERVER_START_INPUT, reason);
         }
 
@@ -3530,17 +3545,7 @@
             int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         // Create statsToken is none exists.
         if (statsToken == null) {
-            // TODO(b/261565259): to avoid using null, add package name in ClientState
-            final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
-            final int uid;
-            if (mCurClient != null) {
-                uid = mCurClient.mUid;
-            } else if (mCurFocusedWindowClient != null) {
-                uid = mCurFocusedWindowClient.mUid;
-            } else {
-                uid = -1;
-            }
-            statsToken = ImeTracker.forLogging().onRequestHide(packageName, uid,
+            statsToken = createStatsTokenForFocusedClient(false /* show */,
                     ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
         }
 
@@ -3775,6 +3780,7 @@
         mCurFocusedWindow = windowToken;
         mCurFocusedWindowSoftInputMode = softInputMode;
         mCurFocusedWindowClient = cs;
+        mCurFocusedWindowEditorInfo = editorInfo;
         mCurPerceptible = true;
 
         // We want to start input before showing the IME, but after closing
@@ -4703,13 +4709,13 @@
                 mWindowManagerInternal.onToggleImeRequested(
                         show, mCurFocusedWindow, requestToken, mCurTokenDisplayId);
         mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
-                mCurFocusedWindowClient, mCurEditorInfo, info.focusedWindowName,
+                mCurFocusedWindowClient, mCurFocusedWindowEditorInfo, info.focusedWindowName,
                 mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
                 info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName,
                 info.imeSurfaceParentName));
 
         if (statsToken != null) {
-            mImeTrackerService.onImmsUpdate(statsToken.mBinder, info.requestWindowName);
+            mImeTrackerService.onImmsUpdate(statsToken, info.requestWindowName);
         }
     }
 
@@ -5751,9 +5757,11 @@
             // We cannot simply distinguish a bad IME that reports an arbitrary package name from
             // an unfortunate IME whose internal state is already obsolete due to the asynchronous
             // nature of our system.  Let's compare it with our internal record.
-            if (!TextUtils.equals(mCurEditorInfo.packageName, packageName)) {
+            final var curPackageName = mCurEditorInfo != null
+                    ? mCurEditorInfo.packageName : null;
+            if (!TextUtils.equals(curPackageName, packageName)) {
                 Slog.e(TAG, "Ignoring createInputContentUriToken mCurEditorInfo.packageName="
-                        + mCurEditorInfo.packageName + " packageName=" + packageName);
+                        + curPackageName + " packageName=" + packageName);
                 return null;
             }
             // This user ID can never bee spoofed.
@@ -6514,6 +6522,30 @@
         return mImeTrackerService;
     }
 
+    /**
+     * Creates an IME request tracking token for the current focused client.
+     *
+     * @param show whether this is a show or a hide request.
+     * @param origin the origin of the IME request.
+     * @param reason the reason why the IME request was created.
+     */
+    @NonNull
+    private ImeTracker.Token createStatsTokenForFocusedClient(boolean show,
+            @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+        final int uid = mCurFocusedWindowClient != null
+                ? mCurFocusedWindowClient.mUid
+                : -1;
+        final var packageName = mCurFocusedWindowEditorInfo != null
+                ? mCurFocusedWindowEditorInfo.packageName
+                : "uid(" + uid + ")";
+
+        if (show) {
+            return ImeTracker.forLogging().onRequestShow(packageName, uid, origin, reason);
+        } else {
+            return ImeTracker.forLogging().onRequestHide(packageName, uid, origin, reason);
+        }
+    }
+
     private static final class InputMethodPrivilegedOperationsImpl
             extends IInputMethodPrivilegedOperations.Stub {
         private final InputMethodManagerService mImms;
diff --git a/services/core/java/com/android/server/rollback/OWNERS b/services/core/java/com/android/server/rollback/OWNERS
index 7feb85f..daa0211 100644
--- a/services/core/java/com/android/server/rollback/OWNERS
+++ b/services/core/java/com/android/server/rollback/OWNERS
@@ -1 +1,3 @@
-olilan@google.com
+ancr@google.com
+harshitmahajan@google.com
+robertogil@google.com
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 8b913db..7a4e7df 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.credentials;
 
 import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN;
+import static android.Manifest.permission.LAUNCH_CREDENTIAL_SELECTOR;
 import static android.content.Context.CREDENTIAL_SERVICE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
@@ -631,7 +632,8 @@
             }
 
             // Send an intent to the UI that we have new enabled providers.
-            getContext().sendBroadcast(IntentFactory.createProviderUpdateIntent());
+            getContext().sendBroadcast(IntentFactory.createProviderUpdateIntent(),
+                    LAUNCH_CREDENTIAL_SELECTOR);
         }
 
         @Override
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 0cd50f0..cc6f7c2 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
@@ -35,6 +35,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -67,7 +68,11 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.hardware.Sensor;
+import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.IDisplayManager;
+import android.hardware.display.IVirtualDisplayCallback;
+import android.hardware.display.VirtualDisplayConfig;
 import android.hardware.input.IInputManager;
 import android.hardware.input.VirtualDpadConfig;
 import android.hardware.input.VirtualKeyEvent;
@@ -144,6 +149,7 @@
     private static final String DEVICE_NAME_3 = "device name 3";
     private static final int DISPLAY_ID_1 = 2;
     private static final int DISPLAY_ID_2 = 3;
+    private static final int NON_EXISTENT_DISPLAY_ID = 42;
     private static final int DEVICE_OWNER_UID_1 = 50;
     private static final int DEVICE_OWNER_UID_2 = 51;
     private static final int UID_1 = 0;
@@ -162,6 +168,8 @@
     private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
     private static final int VIRTUAL_DEVICE_ID_1 = 42;
     private static final int VIRTUAL_DEVICE_ID_2 = 43;
+    private static final VirtualDisplayConfig VIRTUAL_DISPLAY_CONFIG =
+            new VirtualDisplayConfig.Builder("virtual_display", 640, 480, 400).build();
     private static final VirtualDpadConfig DPAD_CONFIG =
             new VirtualDpadConfig.Builder()
                     .setVendorId(VENDOR_ID)
@@ -221,6 +229,8 @@
     @Mock
     private DisplayManagerInternal mDisplayManagerInternalMock;
     @Mock
+    private IDisplayManager mIDisplayManager;
+    @Mock
     private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback;
     @Mock
     private DevicePolicyManager mDevicePolicyManagerMock;
@@ -237,6 +247,8 @@
     @Mock
     private IVirtualDeviceSoundEffectListener mSoundEffectListener;
     @Mock
+    private IVirtualDisplayCallback mVirtualDisplayCallback;
+    @Mock
     private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
     @Mock
     private VirtualDeviceManagerInternal.VirtualDisplayListener mDisplayListener;
@@ -271,9 +283,13 @@
 
     private Intent createRestrictedActivityBlockedIntent(List displayCategories,
             String targetDisplayCategory) {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(displayCategories), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        when(mDisplayManagerInternalMock.createVirtualDisplay(any(), any(), any(), any(),
+                eq(NONBLOCKED_APP_PACKAGE_NAME))).thenReturn(DISPLAY_ID_1);
+        VirtualDisplayConfig config = new VirtualDisplayConfig.Builder("display", 640, 480,
+                420).setDisplayCategories(displayCategories).build();
+        mDeviceImpl.createVirtualDisplay(config, mVirtualDisplayCallback,
+                NONBLOCKED_APP_PACKAGE_NAME);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
@@ -327,6 +343,7 @@
         mContext = Mockito.spy(new ContextWrapper(
                 InstrumentationRegistry.getInstrumentation().getTargetContext()));
         doReturn(mContext).when(mContext).createContextAsUser(eq(Process.myUserHandle()), anyInt());
+        doNothing().when(mContext).sendBroadcastAsUser(any(), any());
         when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
                 mDevicePolicyManagerMock);
 
@@ -369,15 +386,13 @@
 
     @Test
     public void getDeviceIdForDisplayId_nonExistentDisplayId_returnsDefault() {
-        mDeviceImpl.mVirtualDisplayIds.remove(DISPLAY_ID_1);
-
-        assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID_1))
+        assertThat(mVdm.getDeviceIdForDisplayId(NON_EXISTENT_DISPLAY_ID))
                 .isEqualTo(DEVICE_ID_DEFAULT);
     }
 
     @Test
     public void getDeviceIdForDisplayId_withValidVirtualDisplayId_returnsDeviceId() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
 
         assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID_1))
                 .isEqualTo(mDeviceImpl.getDeviceId());
@@ -503,10 +518,9 @@
 
     @Test
     public void getDeviceIdsForUid_differentUidOnDevice_returnsNull() {
-        GenericWindowPolicyController gwpc =
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
-        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1);
-        gwpc.onRunningAppsChanged(Sets.newArraySet(UID_2));
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
+                Sets.newArraySet(UID_2));
 
         Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
         assertThat(deviceIds).isEmpty();
@@ -514,10 +528,9 @@
 
     @Test
     public void getDeviceIdsForUid_oneUidOnDevice_returnsCorrectId() {
-        GenericWindowPolicyController gwpc =
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
-        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1);
-        gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1));
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
+                Sets.newArraySet(UID_1));
 
         Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
         assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId());
@@ -525,10 +538,10 @@
 
     @Test
     public void getDeviceIdsForUid_twoUidsOnDevice_returnsCorrectId() {
-        GenericWindowPolicyController gwpc =
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
-        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1);
-        gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1, UID_2));
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+
+        mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
+                Sets.newArraySet(UID_1, UID_2));
 
         Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
         assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId());
@@ -538,11 +551,10 @@
     public void getDeviceIdsForUid_twoDevicesUidOnOne_returnsCorrectId() {
         VirtualDeviceImpl secondDevice = createVirtualDevice(VIRTUAL_DEVICE_ID_2,
                 DEVICE_OWNER_UID_2);
+        addVirtualDisplay(secondDevice, DISPLAY_ID_2);
 
-        GenericWindowPolicyController gwpc =
-                secondDevice.createWindowPolicyController(new ArrayList<>());
-        secondDevice.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_2);
-        gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1));
+        secondDevice.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_2).onRunningAppsChanged(
+                Sets.newArraySet(UID_1));
 
         Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
         assertThat(deviceIds).containsExactly(secondDevice.getDeviceId());
@@ -550,16 +562,16 @@
 
     @Test
     public void getDeviceIdsForUid_twoDevicesUidOnBoth_returnsCorrectId() {
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         VirtualDeviceImpl secondDevice = createVirtualDevice(VIRTUAL_DEVICE_ID_2,
                 DEVICE_OWNER_UID_2);
-        GenericWindowPolicyController gwpc1 =
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
-        GenericWindowPolicyController gwpc2 =
-                secondDevice.createWindowPolicyController(new ArrayList<>());
-        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc1, DISPLAY_ID_1);
-        secondDevice.onVirtualDisplayCreatedLocked(gwpc2, DISPLAY_ID_2);
-        gwpc1.onRunningAppsChanged(Sets.newArraySet(UID_1));
-        gwpc2.onRunningAppsChanged(Sets.newArraySet(UID_1, UID_2));
+        addVirtualDisplay(secondDevice, DISPLAY_ID_2);
+
+
+        mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
+                Sets.newArraySet(UID_1));
+        secondDevice.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_2).onRunningAppsChanged(
+                Sets.newArraySet(UID_1, UID_2));
 
         Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
         assertThat(deviceIds).containsExactly(
@@ -568,8 +580,7 @@
 
     @Test
     public void getPreferredLocaleListForApp_keyboardAttached_returnLocaleHints() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
-
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
 
         mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), Sets.newArraySet(UID_1));
@@ -609,8 +620,8 @@
                         .setLanguageTag("fr-FR")
                         .build();
 
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
-        secondDevice.mVirtualDisplayIds.add(DISPLAY_ID_2);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        addVirtualDisplay(secondDevice, DISPLAY_ID_2);
 
         mDeviceImpl.createVirtualKeyboard(firstKeyboardConfig, BINDER);
         secondDevice.createVirtualKeyboard(secondKeyboardConfig, secondBinder);
@@ -640,10 +651,9 @@
 
     @Test
     public void onVirtualDisplayRemovedLocked_doesNotThrowException() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         // This call should not throw any exceptions.
-        mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID_1);
+        mDeviceImpl.onVirtualDisplayRemoved(DISPLAY_ID_1);
     }
 
     @Test
@@ -659,8 +669,8 @@
     @Test
     public void onVirtualDisplayRemovedLocked_listenersNotified() {
         mLocalService.registerVirtualDisplayListener(mDisplayListener);
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
 
         mLocalService.onVirtualDisplayRemoved(mDeviceImpl, DISPLAY_ID_1);
         TestableLooper.get(this).processAllMessages();
@@ -723,8 +733,7 @@
         verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
                 nullable(String.class), anyInt(), eq(null));
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
                 nullable(String.class), eq(DISPLAY_ID_1), eq(null));
@@ -733,12 +742,9 @@
     @Test
     public void onVirtualDisplayCreatedLocked_duplicateCalls_onlyOneWakeLockIsAcquired()
             throws RemoteException {
-        GenericWindowPolicyController gwpc = mDeviceImpl.createWindowPolicyController(
-                new ArrayList<>());
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         assertThrows(IllegalStateException.class,
-                () -> mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1));
+                () -> addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1));
         TestableLooper.get(this).processAllMessages();
         verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
@@ -749,13 +755,12 @@
     public void onVirtualDisplayRemovedLocked_unknownDisplayId_throwsException() {
         final int unknownDisplayId = 999;
         assertThrows(IllegalStateException.class,
-                () -> mDeviceImpl.onVirtualDisplayRemovedLocked(unknownDisplayId));
+                () -> mDeviceImpl.onVirtualDisplayRemoved(unknownDisplayId));
     }
 
     @Test
     public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
         TestableLooper.get(this).processAllMessages();
         verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(),
@@ -764,14 +769,13 @@
                 nullable(String.class), eq(DISPLAY_ID_1), eq(null));
 
         IBinder wakeLock = wakeLockCaptor.getValue();
-        mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID_1);
+        mDeviceImpl.onVirtualDisplayRemoved(DISPLAY_ID_1);
         verify(mIPowerManagerMock).releaseWakeLock(eq(wakeLock), anyInt());
     }
 
     @Test
     public void addVirtualDisplay_displayNotReleased_wakeLockIsReleased() throws RemoteException {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
         TestableLooper.get(this).processAllMessages();
         verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(),
@@ -825,7 +829,7 @@
 
     @Test
     public void createVirtualTouchscreen_positiveDisplayDimension_successful() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         VirtualTouchscreenConfig positiveConfig =
                 new VirtualTouchscreenConfig.Builder(
                         /* touchscrenWidth= */ 600, /* touchscreenHeight= */ 800)
@@ -863,7 +867,7 @@
 
     @Test
     public void createVirtualNavigationTouchpad_positiveDisplayDimension_successful() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         VirtualNavigationTouchpadConfig positiveConfig =
                 new VirtualNavigationTouchpadConfig.Builder(
                         /* touchpadHeight= */ 50, /* touchpadWidth= */ 50)
@@ -888,7 +892,7 @@
 
     @Test
     public void createVirtualDpad_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
             assertThrows(SecurityException.class,
                     () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER));
@@ -897,7 +901,7 @@
 
     @Test
     public void createVirtualKeyboard_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
             assertThrows(SecurityException.class,
                     () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
@@ -906,7 +910,7 @@
 
     @Test
     public void createVirtualMouse_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
             assertThrows(SecurityException.class,
                     () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
@@ -915,7 +919,7 @@
 
     @Test
     public void createVirtualTouchscreen_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
             assertThrows(SecurityException.class,
                     () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
@@ -924,7 +928,7 @@
 
     @Test
     public void createVirtualNavigationTouchpad_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
             assertThrows(SecurityException.class,
                     () -> mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG,
@@ -934,7 +938,7 @@
 
     @Test
     public void onAudioSessionStarting_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
             assertThrows(SecurityException.class,
                     () -> mDeviceImpl.onAudioSessionStarting(
@@ -951,7 +955,7 @@
 
     @Test
     public void createVirtualDpad_hasDisplay_obtainFileDescriptor() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER);
         assertWithMessage("Virtual dpad should register fd when the display matches").that(
                 mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -961,7 +965,7 @@
 
     @Test
     public void createVirtualKeyboard_hasDisplay_obtainFileDescriptor() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
         assertWithMessage("Virtual keyboard should register fd when the display matches").that(
                 mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -971,7 +975,7 @@
 
     @Test
     public void createVirtualKeyboard_keyboardCreated_localeUpdated() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
         assertWithMessage("Virtual keyboard should register fd when the display matches")
                 .that(mInputController.getInputDeviceDescriptors())
@@ -992,7 +996,7 @@
                         .setAssociatedDisplayId(DISPLAY_ID_1)
                         .build();
 
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualKeyboard(configWithoutExplicitLayoutInfo, BINDER);
         assertWithMessage("Virtual keyboard should register fd when the display matches")
                 .that(mInputController.getInputDeviceDescriptors())
@@ -1005,7 +1009,7 @@
 
     @Test
     public void virtualDeviceWithoutKeyboard_noLocaleUpdate() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
 
         // no preceding call to createVirtualKeyboard()
         assertThat(mDeviceImpl.getDeviceLocaleList()).isNull();
@@ -1013,7 +1017,7 @@
 
     @Test
     public void createVirtualMouse_hasDisplay_obtainFileDescriptor() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER);
         assertWithMessage("Virtual mouse should register fd when the display matches").that(
                 mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -1023,7 +1027,7 @@
 
     @Test
     public void createVirtualTouchscreen_hasDisplay_obtainFileDescriptor() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER);
         assertWithMessage("Virtual touchscreen should register fd when the display matches").that(
                 mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -1033,7 +1037,7 @@
 
     @Test
     public void createVirtualNavigationTouchpad_hasDisplay_obtainFileDescriptor() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG, BINDER);
         assertWithMessage("Virtual navigation touchpad should register fd when the display matches")
                 .that(
@@ -1055,8 +1059,7 @@
 
     @Test
     public void onAudioSessionStarting_hasVirtualAudioController() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
 
         mDeviceImpl.onAudioSessionStarting(DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback);
 
@@ -1065,8 +1068,7 @@
 
     @Test
     public void onAudioSessionEnded_noVirtualAudioController() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.onAudioSessionStarting(DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback);
 
         mDeviceImpl.onAudioSessionEnded();
@@ -1076,8 +1078,7 @@
 
     @Test
     public void close_cleanVirtualAudioController() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.onAudioSessionStarting(DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback);
 
         mDeviceImpl.close();
@@ -1321,9 +1322,9 @@
 
     @Test
     public void setShowPointerIcon_setsValueForAllDisplays() {
-        mDeviceImpl.mVirtualDisplayIds.add(1);
-        mDeviceImpl.mVirtualDisplayIds.add(2);
-        mDeviceImpl.mVirtualDisplayIds.add(3);
+        addVirtualDisplay(mDeviceImpl, 1);
+        addVirtualDisplay(mDeviceImpl, 2);
+        addVirtualDisplay(mDeviceImpl, 3);
         VirtualMouseConfig config1 = new VirtualMouseConfig.Builder()
                 .setAssociatedDisplayId(1)
                 .setInputDeviceName(DEVICE_NAME_1)
@@ -1346,7 +1347,9 @@
         mDeviceImpl.createVirtualMouse(config1, BINDER);
         mDeviceImpl.createVirtualMouse(config2, BINDER);
         mDeviceImpl.createVirtualMouse(config3, BINDER);
+        clearInvocations(mInputManagerInternalMock);
         mDeviceImpl.setShowPointerIcon(false);
+
         verify(mInputManagerInternalMock, times(3)).setPointerIconVisible(eq(false), anyInt());
         verify(mInputManagerInternalMock, never()).setPointerIconVisible(eq(true), anyInt());
         mDeviceImpl.setShowPointerIcon(true);
@@ -1355,9 +1358,8 @@
 
     @Test
     public void openNonBlockedAppOnVirtualDisplay_doesNotStartBlockedAlertActivity() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
@@ -1376,9 +1378,8 @@
 
     @Test
     public void openPermissionControllerOnVirtualDisplay_startBlockedAlertActivity() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
@@ -1397,9 +1398,8 @@
 
     @Test
     public void openSettingsOnVirtualDisplay_startBlockedAlertActivity() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
@@ -1418,9 +1418,8 @@
 
     @Test
     public void openVendingOnVirtualDisplay_startBlockedAlertActivity() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
@@ -1439,9 +1438,8 @@
 
     @Test
     public void openGoogleDialerOnVirtualDisplay_startBlockedAlertActivity() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
@@ -1460,9 +1458,8 @@
 
     @Test
     public void openGoogleMapsOnVirtualDisplay_startBlockedAlertActivity() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
@@ -1482,9 +1479,8 @@
     @Test
     public void registerRunningAppsChangedListener_onRunningAppsChanged_listenersNotified() {
         ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
 
         gwpc.onRunningAppsChanged(uids);
@@ -1497,11 +1493,10 @@
     @Test
     public void noRunningAppsChangedListener_onRunningAppsChanged_doesNotThrowException() {
         ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
-        mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID_1);
+        gwpc.unregisterRunningAppsChangedListener(mDeviceImpl);
 
         // This call should not throw any exceptions.
         gwpc.onRunningAppsChanged(uids);
@@ -1512,9 +1507,8 @@
     @Test
     public void canActivityBeLaunched_activityCanLaunch() {
         Intent intent = new Intent(ACTION_VIEW, Uri.parse(TEST_SITE));
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 NONBLOCKED_APP_PACKAGE_NAME,
@@ -1537,9 +1531,8 @@
         doReturn(interceptor).when(interceptor).asBinder();
         doReturn(interceptor).when(interceptor).queryLocalInterface(anyString());
 
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 NONBLOCKED_APP_PACKAGE_NAME,
@@ -1581,9 +1574,8 @@
         doReturn(interceptor).when(interceptor).asBinder();
         doReturn(interceptor).when(interceptor).queryLocalInterface(anyString());
 
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 NONBLOCKED_APP_PACKAGE_NAME,
@@ -1626,8 +1618,7 @@
     }
 
     @Test
-    public void
-            restrictedActivityOnNonMatchingRestrictedVirtualDisplay_startBlockedAlertActivity() {
+    public void restrictedActivityNonMatchingRestrictedVirtualDisplay_startBlockedAlertActivity() {
         Intent blockedAppIntent = createRestrictedActivityBlockedIntent(List.of("abc"), "def");
         verify(mContext).startActivityAsUser(argThat(intent ->
                 intent.filterEquals(blockedAppIntent)), any(), any());
@@ -1654,15 +1645,15 @@
 
     @Test
     public void getDisplayIdsForDevice_oneDisplay_resultContainsCorrectDisplayId() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         ArraySet<Integer> displayIds = mLocalService.getDisplayIdsForDevice(VIRTUAL_DEVICE_ID_1);
         assertThat(displayIds).containsExactly(DISPLAY_ID_1);
     }
 
     @Test
     public void getDisplayIdsForDevice_twoDisplays_resultContainsCorrectDisplayIds() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_2);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_2);
         ArraySet<Integer> displayIds = mLocalService.getDisplayIdsForDevice(VIRTUAL_DEVICE_ID_1);
         assertThat(displayIds).containsExactly(DISPLAY_ID_1, DISPLAY_ID_2);
     }
@@ -1677,15 +1668,22 @@
     private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid,
             VirtualDeviceParams params) {
         VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext,
-                mAssociationInfo, new Binder(), ownerUid, virtualDeviceId,
-                mInputController, mSensorController, mCameraAccessController,
-                /* onDeviceCloseListener= */ deviceId -> mVdms.removeVirtualDevice(deviceId),
+                mAssociationInfo, mVdms, new Binder(), ownerUid, virtualDeviceId,
+                mInputController, mSensorController, mCameraAccessController
+                /* onDeviceCloseListener= */ /*deviceId -> mVdms.removeVirtualDevice(deviceId)*/,
                 mPendingTrampolineCallback, mActivityListener, mSoundEffectListener,
-                mRunningAppsChangedCallback, params);
+                mRunningAppsChangedCallback, params, new DisplayManagerGlobal(mIDisplayManager));
         mVdms.addVirtualDevice(virtualDeviceImpl);
         return virtualDeviceImpl;
     }
 
+    private void addVirtualDisplay(VirtualDeviceImpl virtualDevice, int displayId) {
+        when(mDisplayManagerInternalMock.createVirtualDisplay(any(), eq(mVirtualDisplayCallback),
+                eq(virtualDevice), any(), any())).thenReturn(displayId);
+        virtualDevice.createVirtualDisplay(VIRTUAL_DISPLAY_CONFIG, mVirtualDisplayCallback,
+                NONBLOCKED_APP_PACKAGE_NAME);
+    }
+
     /** Helper class to drop permissions temporarily and restore them at the end of a test. */
     static final class DropShellPermissionsTemporarily implements AutoCloseable {
         DropShellPermissionsTemporarily() {
diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
index b921838..4c0361d 100644
--- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
+++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
@@ -263,7 +263,7 @@
                 + instrumentation.getContext().getUserId() + " " + RoleManager.ROLE_HOME + " "
                 + packageName + " 0");
         waitUntil("Failed to get shortcut access",
-                () -> hasShortcutAccess(instrumentation, packageName), 20);
+                () -> hasShortcutAccess(instrumentation, packageName), 60);
     }
 
     public static void setDefaultLauncher(Instrumentation instrumentation, Context packageContext) {
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index 6b2bea0..97538c1 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -76,7 +76,10 @@
     }
 
     /**
-     * Request Telecom set the call state to active.
+     * Request Telecom set the call state to active. This method should be called when either an
+     * outgoing call is ready to go active or a held call is ready to go active again. For incoming
+     * calls that are ready to be answered, use
+     * {@link CallControl#answer(int, Executor, OutcomeReceiver)}.
      *
      * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
      *                 will be called on.
@@ -106,6 +109,43 @@
     }
 
     /**
+     * Request Telecom answer an incoming call.  For outgoing calls and calls that have been placed
+     * on hold, use {@link CallControl#setActive(Executor, OutcomeReceiver)}.
+     *
+     * @param videoState to report to Telecom. Telecom will store VideoState in the event another
+     *                   service/device requests it in order to continue the call on another screen.
+     * @param executor   The {@link Executor} on which the {@link OutcomeReceiver} callback
+     *                   will be called on.
+     * @param callback   that will be completed on the Telecom side that details success or failure
+     *                   of the requested operation.
+     *
+     *                   {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
+     *                   switched the call state to active
+     *
+     *                   {@link OutcomeReceiver#onError} will be called if Telecom has failed to set
+     *                   the call state to active.  A {@link CallException} will be passed
+     *                   that details why the operation failed.
+     */
+    public void answer(@android.telecom.CallAttributes.CallType int videoState,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, CallException> callback) {
+        validateVideoState(videoState);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        if (mServerInterface != null) {
+            try {
+                mServerInterface.answer(videoState, mCallId,
+                        new CallControlResultReceiver("answer", executor, callback));
+
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        } else {
+            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        }
+    }
+
+    /**
      * Request Telecom set the call state to inactive. This the same as hold for two call endpoints
      * but can be extended to setting a meeting to inactive.
      *
@@ -343,4 +383,13 @@
         }
     }
 
+    /** @hide */
+    private void validateVideoState(@android.telecom.CallAttributes.CallType int videoState) {
+        if (videoState != CallAttributes.AUDIO_CALL && videoState != CallAttributes.VIDEO_CALL) {
+            throw new IllegalArgumentException(TextUtils.formatSimple(
+                    "The VideoState argument passed in, %d , is not a valid VideoState. The "
+                            + "VideoState choices are limited to CallAttributes.AUDIO_CALL or"
+                            + "CallAttributes.VIDEO_CALL", videoState));
+        }
+    }
 }
diff --git a/telecomm/java/com/android/internal/telecom/ICallControl.aidl b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
index 3e651e9..5e2c923 100644
--- a/telecomm/java/com/android/internal/telecom/ICallControl.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
@@ -27,6 +27,7 @@
  */
 oneway interface ICallControl {
     void setActive(String callId, in ResultReceiver callback);
+    void answer(int videoState, String callId, in ResultReceiver callback);
     void setInactive(String callId, in ResultReceiver callback);
     void disconnect(String callId, in DisconnectCause disconnectCause, in ResultReceiver callback);
     void startCallStreaming(String callId, in ResultReceiver callback);