Merge "Fix convo name / icon overlap on low DPI" into rvc-dev
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
index 483d2cc..9c1acaf 100644
--- a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
+++ b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
@@ -184,9 +184,8 @@
      * @throws SecurityException when the caller is not allowed to create a session, such
      *                           as when called from an Instant app.
      * @throws IllegalArgumentException when {@code blobHandle} is invalid.
-     * @throws IllegalStateException when a new session could not be created, such as when the
-     *                               caller is trying to create too many sessions or when the
-     *                               device is running low on space.
+     * @throws LimitExceededException when a new session could not be created, such as when the
+     *                                caller is trying to create too many sessions.
      */
     public @IntRange(from = 1) long createSession(@NonNull BlobHandle blobHandle)
             throws IOException {
@@ -194,6 +193,7 @@
             return mService.createSession(blobHandle, mContext.getOpPackageName());
         } catch (ParcelableException e) {
             e.maybeRethrow(IOException.class);
+            e.maybeRethrow(LimitExceededException.class);
             throw new RuntimeException(e);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -302,8 +302,9 @@
      *                                  if the {@code leaseExpiryTimeMillis} is greater than the
      *                                  {@link BlobHandle#getExpiryTimeMillis()}.
      * @throws LimitExceededException when a lease could not be acquired, such as when the
-     *                                caller is trying to acquire leases on too much data. Apps
-     *                                can avoid this by checking the remaining quota using
+     *                                caller is trying to acquire too many leases or acquire
+     *                                leases on too much data. Apps can avoid this by checking
+     *                                the remaining quota using
      *                                {@link #getRemainingLeaseQuotaBytes()} before trying to
      *                                acquire a lease.
      *
@@ -362,8 +363,9 @@
      *                                  if the {@code leaseExpiryTimeMillis} is greater than the
      *                                  {@link BlobHandle#getExpiryTimeMillis()}.
      * @throws LimitExceededException when a lease could not be acquired, such as when the
-     *                                caller is trying to acquire leases on too much data. Apps
-     *                                can avoid this by checking the remaining quota using
+     *                                caller is trying to acquire too many leases or acquire
+     *                                leases on too much data. Apps can avoid this by checking
+     *                                the remaining quota using
      *                                {@link #getRemainingLeaseQuotaBytes()} before trying to
      *                                acquire a lease.
      *
@@ -415,8 +417,9 @@
      *                           exist or the caller does not have access to it.
      * @throws IllegalArgumentException when {@code blobHandle} is invalid.
      * @throws LimitExceededException when a lease could not be acquired, such as when the
-     *                                caller is trying to acquire leases on too much data. Apps
-     *                                can avoid this by checking the remaining quota using
+     *                                caller is trying to acquire too many leases or acquire
+     *                                leases on too much data. Apps can avoid this by checking
+     *                                the remaining quota using
      *                                {@link #getRemainingLeaseQuotaBytes()} before trying to
      *                                acquire a lease.
      *
@@ -462,8 +465,9 @@
      *                           exist or the caller does not have access to it.
      * @throws IllegalArgumentException when {@code blobHandle} is invalid.
      * @throws LimitExceededException when a lease could not be acquired, such as when the
-     *                                caller is trying to acquire leases on too much data. Apps
-     *                                can avoid this by checking the remaining quota using
+     *                                caller is trying to acquire too many leases or acquire
+     *                                leases on too much data. Apps can avoid this by checking
+     *                                the remaining quota using
      *                                {@link #getRemainingLeaseQuotaBytes()} before trying to
      *                                acquire a lease.
      *
@@ -757,6 +761,8 @@
          * @throws SecurityException when the caller is not the owner of the session.
          * @throws IllegalStateException when the caller tries to change access for a blob which is
          *                               already committed.
+         * @throws LimitExceededException when the caller tries to explicitly allow too
+         *                                many packages using this API.
          */
         public void allowPackageAccess(@NonNull String packageName, @NonNull byte[] certificate)
                 throws IOException {
@@ -764,6 +770,7 @@
                 mSession.allowPackageAccess(packageName, certificate);
             } catch (ParcelableException e) {
                 e.maybeRethrow(IOException.class);
+                e.maybeRethrow(LimitExceededException.class);
                 throw new RuntimeException(e);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
index 265479f..79cd1b1 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
@@ -141,6 +141,36 @@
         public static long DELETE_ON_LAST_LEASE_DELAY_MS =
                 DEFAULT_DELETE_ON_LAST_LEASE_DELAY_MS;
 
+        /**
+         * Denotes the maximum number of active sessions per app at any time.
+         */
+        public static final String KEY_MAX_ACTIVE_SESSIONS = "max_active_sessions";
+        public static int DEFAULT_MAX_ACTIVE_SESSIONS = 250;
+        public static int MAX_ACTIVE_SESSIONS = DEFAULT_MAX_ACTIVE_SESSIONS;
+
+        /**
+         * Denotes the maximum number of committed blobs per app at any time.
+         */
+        public static final String KEY_MAX_COMMITTED_BLOBS = "max_committed_blobs";
+        public static int DEFAULT_MAX_COMMITTED_BLOBS = 1000;
+        public static int MAX_COMMITTED_BLOBS = DEFAULT_MAX_COMMITTED_BLOBS;
+
+        /**
+         * Denotes the maximum number of leased blobs per app at any time.
+         */
+        public static final String KEY_MAX_LEASED_BLOBS = "max_leased_blobs";
+        public static int DEFAULT_MAX_LEASED_BLOBS = 500;
+        public static int MAX_LEASED_BLOBS = DEFAULT_MAX_LEASED_BLOBS;
+
+        /**
+         * Denotes the maximum number of packages explicitly permitted to access a blob
+         * (permitted as part of creating a {@link BlobAccessMode}).
+         */
+        public static final String KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES = "max_permitted_pks";
+        public static int DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES = 300;
+        public static int MAX_BLOB_ACCESS_PERMITTED_PACKAGES =
+                DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES;
+
         static void refresh(Properties properties) {
             if (!NAMESPACE_BLOBSTORE.equals(properties.getNamespace())) {
                 return;
@@ -178,6 +208,19 @@
                         DELETE_ON_LAST_LEASE_DELAY_MS = properties.getLong(key,
                                 DEFAULT_DELETE_ON_LAST_LEASE_DELAY_MS);
                         break;
+                    case KEY_MAX_ACTIVE_SESSIONS:
+                        MAX_ACTIVE_SESSIONS = properties.getInt(key, DEFAULT_MAX_ACTIVE_SESSIONS);
+                        break;
+                    case KEY_MAX_COMMITTED_BLOBS:
+                        MAX_COMMITTED_BLOBS = properties.getInt(key, DEFAULT_MAX_COMMITTED_BLOBS);
+                        break;
+                    case KEY_MAX_LEASED_BLOBS:
+                        MAX_LEASED_BLOBS = properties.getInt(key, DEFAULT_MAX_LEASED_BLOBS);
+                        break;
+                    case KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES:
+                        MAX_BLOB_ACCESS_PERMITTED_PACKAGES = properties.getInt(key,
+                                DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES);
+                        break;
                     default:
                         Slog.wtf(TAG, "Unknown key in device config properties: " + key);
                 }
@@ -210,6 +253,15 @@
             fout.println(String.format(dumpFormat, KEY_DELETE_ON_LAST_LEASE_DELAY_MS,
                     TimeUtils.formatDuration(DELETE_ON_LAST_LEASE_DELAY_MS),
                     TimeUtils.formatDuration(DEFAULT_DELETE_ON_LAST_LEASE_DELAY_MS)));
+            fout.println(String.format(dumpFormat, KEY_MAX_ACTIVE_SESSIONS,
+                    MAX_ACTIVE_SESSIONS, DEFAULT_MAX_ACTIVE_SESSIONS));
+            fout.println(String.format(dumpFormat, KEY_MAX_COMMITTED_BLOBS,
+                    MAX_COMMITTED_BLOBS, DEFAULT_MAX_COMMITTED_BLOBS));
+            fout.println(String.format(dumpFormat, KEY_MAX_LEASED_BLOBS,
+                    MAX_LEASED_BLOBS, DEFAULT_MAX_LEASED_BLOBS));
+            fout.println(String.format(dumpFormat, KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES,
+                    MAX_BLOB_ACCESS_PERMITTED_PACKAGES,
+                    DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES));
         }
     }
 
@@ -288,6 +340,34 @@
         return DeviceConfigProperties.DELETE_ON_LAST_LEASE_DELAY_MS;
     }
 
+    /**
+     * Returns the maximum number of active sessions per app.
+     */
+    public static int getMaxActiveSessions() {
+        return DeviceConfigProperties.MAX_ACTIVE_SESSIONS;
+    }
+
+    /**
+     * Returns the maximum number of committed blobs per app.
+     */
+    public static int getMaxCommittedBlobs() {
+        return DeviceConfigProperties.MAX_COMMITTED_BLOBS;
+    }
+
+    /**
+     * Returns the maximum number of leased blobs per app.
+     */
+    public static int getMaxLeasedBlobs() {
+        return DeviceConfigProperties.MAX_LEASED_BLOBS;
+    }
+
+    /**
+     * Returns the maximum number of packages explicitly permitted to access a blob.
+     */
+    public static int getMaxPermittedPackages() {
+        return DeviceConfigProperties.MAX_BLOB_ACCESS_PERMITTED_PACKAGES;
+    }
+
     @Nullable
     public static File prepareBlobFile(long sessionId) {
         final File blobsDir = prepareBlobsDir();
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index a90536fe..7a6c884 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -35,6 +35,9 @@
 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT;
 import static com.android.server.blob.BlobStoreConfig.getAdjustedCommitTimeMs;
 import static com.android.server.blob.BlobStoreConfig.getDeletionOnLastLeaseDelayMs;
+import static com.android.server.blob.BlobStoreConfig.getMaxActiveSessions;
+import static com.android.server.blob.BlobStoreConfig.getMaxCommittedBlobs;
+import static com.android.server.blob.BlobStoreConfig.getMaxLeasedBlobs;
 import static com.android.server.blob.BlobStoreSession.STATE_ABANDONED;
 import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED;
 import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_INVALID;
@@ -124,6 +127,7 @@
 import java.util.Objects;
 import java.util.Random;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -332,9 +336,26 @@
         mKnownBlobIds.add(id);
     }
 
+    @GuardedBy("mBlobsLock")
+    private int getSessionsCountLocked(int uid, String packageName) {
+        // TODO: Maintain a counter instead of traversing all the sessions
+        final AtomicInteger sessionsCount = new AtomicInteger(0);
+        forEachSessionInUser(session -> {
+            if (session.getOwnerUid() == uid && session.getOwnerPackageName().equals(packageName)) {
+                sessionsCount.getAndIncrement();
+            }
+        }, UserHandle.getUserId(uid));
+        return sessionsCount.get();
+    }
+
     private long createSessionInternal(BlobHandle blobHandle,
             int callingUid, String callingPackage) {
         synchronized (mBlobsLock) {
+            final int sessionsCount = getSessionsCountLocked(callingUid, callingPackage);
+            if (sessionsCount >= getMaxActiveSessions()) {
+                throw new LimitExceededException("Too many active sessions for the caller: "
+                        + sessionsCount);
+            }
             // TODO: throw if there is already an active session associated with blobHandle.
             final long sessionId = generateNextSessionIdLocked();
             final BlobStoreSession session = new BlobStoreSession(mContext,
@@ -408,10 +429,39 @@
         }
     }
 
+    @GuardedBy("mBlobsLock")
+    private int getCommittedBlobsCountLocked(int uid, String packageName) {
+        // TODO: Maintain a counter instead of traversing all the blobs
+        final AtomicInteger blobsCount = new AtomicInteger(0);
+        forEachBlobInUser((blobMetadata) -> {
+            if (blobMetadata.isACommitter(packageName, uid)) {
+                blobsCount.getAndIncrement();
+            }
+        }, UserHandle.getUserId(uid));
+        return blobsCount.get();
+    }
+
+    @GuardedBy("mBlobsLock")
+    private int getLeasedBlobsCountLocked(int uid, String packageName) {
+        // TODO: Maintain a counter instead of traversing all the blobs
+        final AtomicInteger blobsCount = new AtomicInteger(0);
+        forEachBlobInUser((blobMetadata) -> {
+            if (blobMetadata.isALeasee(packageName, uid)) {
+                blobsCount.getAndIncrement();
+            }
+        }, UserHandle.getUserId(uid));
+        return blobsCount.get();
+    }
+
     private void acquireLeaseInternal(BlobHandle blobHandle, int descriptionResId,
             CharSequence description, long leaseExpiryTimeMillis,
             int callingUid, String callingPackage) {
         synchronized (mBlobsLock) {
+            final int leasesCount = getLeasedBlobsCountLocked(callingUid, callingPackage);
+            if (leasesCount >= getMaxLeasedBlobs()) {
+                throw new LimitExceededException("Too many leased blobs for the caller: "
+                        + leasesCount);
+            }
             final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
                     .get(blobHandle);
             if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
@@ -626,6 +676,18 @@
                 break;
             case STATE_VERIFIED_VALID:
                 synchronized (mBlobsLock) {
+                    final int committedBlobsCount = getCommittedBlobsCountLocked(
+                            session.getOwnerUid(), session.getOwnerPackageName());
+                    if (committedBlobsCount >= getMaxCommittedBlobs()) {
+                        Slog.d(TAG, "Failed to commit: too many committed blobs. count: "
+                                + committedBlobsCount + "; blob: " + session);
+                        session.sendCommitCallbackResult(COMMIT_RESULT_ERROR);
+                        session.getSessionFile().delete();
+                        mActiveBlobIds.remove(session.getSessionId());
+                        getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid()))
+                                .remove(session.getSessionId());
+                        break;
+                    }
                     final int userId = UserHandle.getUserId(session.getOwnerUid());
                     final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(
                             userId);
@@ -656,7 +718,7 @@
                         } else {
                             blob.addOrReplaceCommitter(existingCommitter);
                         }
-                        Slog.d(TAG, "Error committing the blob", e);
+                        Slog.d(TAG, "Error committing the blob: " + session, e);
                         FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED,
                                 session.getOwnerUid(), blob.getBlobId(), blob.getSize(),
                                 FrameworkStatsLog.BLOB_COMMITTED__RESULT__ERROR_DURING_COMMIT);
@@ -1096,6 +1158,16 @@
 
     void runClearAllSessions(@UserIdInt int userId) {
         synchronized (mBlobsLock) {
+            for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
+                final int sessionUserId = mSessions.keyAt(i);
+                if (userId != UserHandle.USER_ALL && userId != sessionUserId) {
+                    continue;
+                }
+                final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i);
+                for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) {
+                    mActiveBlobIds.remove(userSessions.valueAt(j).getSessionId());
+                }
+            }
             if (userId == UserHandle.USER_ALL) {
                 mSessions.clear();
             } else {
@@ -1107,6 +1179,16 @@
 
     void runClearAllBlobs(@UserIdInt int userId) {
         synchronized (mBlobsLock) {
+            for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
+                final int blobUserId = mBlobsMap.keyAt(i);
+                if (userId != UserHandle.USER_ALL && userId != blobUserId) {
+                    continue;
+                }
+                final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
+                for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
+                    mActiveBlobIds.remove(userBlobs.valueAt(j).getBlobId());
+                }
+            }
             if (userId == UserHandle.USER_ALL) {
                 mBlobsMap.clear();
             } else {
@@ -1331,8 +1413,11 @@
                         + "callingUid=" + callingUid + ", callingPackage=" + packageName);
             }
 
-            // TODO: Verify caller request is within limits (no. of calls/blob sessions/blobs)
-            return createSessionInternal(blobHandle, callingUid, packageName);
+            try {
+                return createSessionInternal(blobHandle, callingUid, packageName);
+            } catch (LimitExceededException e) {
+                throw new ParcelableException(e);
+            }
         }
 
         @Override
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
index baafff5..53e296b 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
@@ -32,6 +32,7 @@
 
 import static com.android.server.blob.BlobStoreConfig.TAG;
 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_SESSION_CREATION_TIME;
+import static com.android.server.blob.BlobStoreConfig.getMaxPermittedPackages;
 import static com.android.server.blob.BlobStoreConfig.hasSessionExpired;
 
 import android.annotation.BytesLong;
@@ -43,7 +44,9 @@
 import android.content.Context;
 import android.os.Binder;
 import android.os.FileUtils;
+import android.os.LimitExceededException;
 import android.os.ParcelFileDescriptor;
+import android.os.ParcelableException;
 import android.os.RemoteException;
 import android.os.RevocableFileDescriptor;
 import android.os.Trace;
@@ -76,7 +79,10 @@
 import java.util.Arrays;
 import java.util.Objects;
 
-/** TODO: add doc */
+/**
+ * Class to represent the state corresponding to an ongoing
+ * {@link android.app.blob.BlobStoreManager.Session}
+ */
 @VisibleForTesting
 class BlobStoreSession extends IBlobStoreSession.Stub {
 
@@ -326,6 +332,11 @@
                 throw new IllegalStateException("Not allowed to change access type in state: "
                         + stateToString(mState));
             }
+            if (mBlobAccessMode.getNumWhitelistedPackages() >= getMaxPermittedPackages()) {
+                throw new ParcelableException(new LimitExceededException(
+                        "Too many packages permitted to access the blob: "
+                                + mBlobAccessMode.getNumWhitelistedPackages()));
+            }
             mBlobAccessMode.allowPackageAccess(packageName, certificate);
         }
     }
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index 8a3fbde..0c8c9a9 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -841,6 +841,43 @@
      */
     public static final String PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT =
             "android.media.mediaParser.exposeChunkIndexAsMediaFormat";
+    /**
+     * Sets a list of closed-caption {@link MediaFormat MediaFormats} that should be exposed as part
+     * of the extracted media. {@code List<MediaFormat>} expected. Default value is an empty list.
+     *
+     * <p>Expected keys in the {@link MediaFormat} are:
+     *
+     * <ul>
+     *   <p>{@link MediaFormat#KEY_MIME}: Determine the type of captions (for example,
+     *   application/cea-608). Mandatory.
+     *   <p>{@link MediaFormat#KEY_CAPTION_SERVICE_NUMBER}: Determine the channel on which the
+     *   captions are transmitted. Optional.
+     * </ul>
+     *
+     * @hide
+     */
+    public static final String PARAMETER_EXPOSE_CAPTION_FORMATS =
+            "android.media.mediaParser.exposeCaptionFormats";
+    /**
+     * Sets whether the value associated with {@link #PARAMETER_EXPOSE_CAPTION_FORMATS} should
+     * override any in-band caption service declarations. {@code boolean} expected. Default value is
+     * {@link false}.
+     *
+     * <p>When {@code false}, any present in-band caption services information will override the
+     * values associated with {@link #PARAMETER_EXPOSE_CAPTION_FORMATS}.
+     *
+     * @hide
+     */
+    public static final String PARAMETER_OVERRIDE_IN_BAND_CAPTION_DECLARATIONS =
+            "android.media.mediaParser.overrideInBandCaptionDeclarations";
+    /**
+     * Sets whether a track for EMSG events should be exposed in case of parsing a container that
+     * supports them. {@code boolean} expected. Default value is {@link false}.
+     *
+     * @hide
+     */
+    public static final String PARAMETER_EXPOSE_EMSG_TRACK =
+            "android.media.mediaParser.exposeEmsgTrack";
 
     // Private constants.
 
@@ -851,6 +888,7 @@
     private static final String TS_MODE_MULTI_PMT = "multi_pmt";
     private static final String TS_MODE_HLS = "hls";
     private static final int BYTES_PER_SUBSAMPLE_ENCRYPTION_ENTRY = 6;
+    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
 
     @IntDef(
             value = {
@@ -1000,6 +1038,7 @@
     private final DataReaderAdapter mScratchDataReaderAdapter;
     private final ParsableByteArrayAdapter mScratchParsableByteArrayAdapter;
     @Nullable private final Constructor<DrmInitData.SchemeInitData> mSchemeInitDataConstructor;
+    private final ArrayList<Format> mMuxedCaptionFormats;
     private boolean mInBandCryptoInfo;
     private boolean mIncludeSupplementalData;
     private boolean mIgnoreTimestampOffset;
@@ -1071,6 +1110,9 @@
         if (PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT.equals(parameterName)) {
             mExposeChunkIndexAsMediaFormat = (boolean) value;
         }
+        if (PARAMETER_EXPOSE_CAPTION_FORMATS.equals(parameterName)) {
+            setMuxedCaptionFormats((List<MediaFormat>) value);
+        }
         mParserParameters.put(parameterName, value);
         return this;
     }
@@ -1109,8 +1151,8 @@
      *
      * <p>This method will block until some progress has been made.
      *
-     * <p>If this instance was created using {@link #create}. the first call to this method will
-     * sniff the content with the parsers with the provided names.
+     * <p>If this instance was created using {@link #create}, the first call to this method will
+     * sniff the content using the selected parser implementations.
      *
      * @param seekableInputReader The {@link SeekableInputReader} from which to obtain the media
      *     container data.
@@ -1242,6 +1284,14 @@
         mScratchDataReaderAdapter = new DataReaderAdapter();
         mScratchParsableByteArrayAdapter = new ParsableByteArrayAdapter();
         mSchemeInitDataConstructor = getSchemeInitDataConstructor();
+        mMuxedCaptionFormats = new ArrayList<>();
+    }
+
+    private void setMuxedCaptionFormats(List<MediaFormat> mediaFormats) {
+        mMuxedCaptionFormats.clear();
+        for (MediaFormat mediaFormat : mediaFormats) {
+            mMuxedCaptionFormats.add(toExoPlayerCaptionFormat(mediaFormat));
+        }
     }
 
     private boolean isPendingSeek() {
@@ -1268,6 +1318,10 @@
                 return new MatroskaExtractor(flags);
             case PARSER_NAME_FMP4:
                 flags |=
+                        getBooleanParameter(PARAMETER_EXPOSE_EMSG_TRACK)
+                                ? FragmentedMp4Extractor.FLAG_ENABLE_EMSG_TRACK
+                                : 0;
+                flags |=
                         getBooleanParameter(PARAMETER_MP4_IGNORE_EDIT_LISTS)
                                 ? FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS
                                 : 0;
@@ -1280,7 +1334,11 @@
                                 ? FragmentedMp4Extractor
                                         .FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
                                 : 0;
-                return new FragmentedMp4Extractor(flags, timestampAdjuster);
+                return new FragmentedMp4Extractor(
+                        flags,
+                        timestampAdjuster,
+                        /* sideloadedTrack= */ null,
+                        mMuxedCaptionFormats);
             case PARSER_NAME_MP4:
                 flags |=
                         getBooleanParameter(PARAMETER_MP4_IGNORE_EDIT_LISTS)
@@ -1331,6 +1389,10 @@
                         getBooleanParameter(PARAMETER_TS_IGNORE_SPLICE_INFO_STREAM)
                                 ? DefaultTsPayloadReaderFactory.FLAG_IGNORE_SPLICE_INFO_STREAM
                                 : 0;
+                flags |=
+                        getBooleanParameter(PARAMETER_OVERRIDE_IN_BAND_CAPTION_DECLARATIONS)
+                                ? DefaultTsPayloadReaderFactory.FLAG_OVERRIDE_CAPTION_DESCRIPTORS
+                                : 0;
                 String tsMode = getStringParameter(PARAMETER_TS_MODE, TS_MODE_SINGLE_PMT);
                 int hlsMode =
                         TS_MODE_SINGLE_PMT.equals(tsMode)
@@ -1343,7 +1405,7 @@
                         timestampAdjuster != null
                                 ? timestampAdjuster
                                 : new TimestampAdjuster(/* firstSampleTimestampUs= */ 0),
-                        new DefaultTsPayloadReaderFactory(flags));
+                        new DefaultTsPayloadReaderFactory(flags, mMuxedCaptionFormats));
             case PARSER_NAME_FLV:
                 return new FlvExtractor();
             case PARSER_NAME_OGG:
@@ -1625,6 +1687,9 @@
                 if (cryptoData != mLastReceivedCryptoData) {
                     mLastOutputCryptoInfo =
                             createNewCryptoInfoAndPopulateWithCryptoData(cryptoData);
+                    // We are using in-band crypto info, so the IV will be ignored. But we prevent
+                    // it from being null because toString assumes it non-null.
+                    mLastOutputCryptoInfo.iv = EMPTY_BYTE_ARRAY;
                 }
             } else /* We must populate the full CryptoInfo. */ {
                 // CryptoInfo.pattern is not accessible to the user, so the user needs to feed
@@ -1789,6 +1854,16 @@
 
     // Private static methods.
 
+    private static Format toExoPlayerCaptionFormat(MediaFormat mediaFormat) {
+        Format.Builder formatBuilder =
+                new Format.Builder().setSampleMimeType(mediaFormat.getString(MediaFormat.KEY_MIME));
+        if (mediaFormat.containsKey(MediaFormat.KEY_CAPTION_SERVICE_NUMBER)) {
+            formatBuilder.setAccessibilityChannel(
+                    mediaFormat.getInteger(MediaFormat.KEY_CAPTION_SERVICE_NUMBER));
+        }
+        return formatBuilder.build();
+    }
+
     private static MediaFormat toMediaFormat(Format format) {
         MediaFormat result = new MediaFormat();
         setOptionalMediaFormatInt(result, MediaFormat.KEY_BIT_RATE, format.bitrate);
@@ -1857,8 +1932,10 @@
             // format for convenient use from ExoPlayer.
             result.setString("crypto-mode-fourcc", format.drmInitData.schemeType);
         }
+        if (format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) {
+            result.setLong("subsample-offset-us-long", format.subsampleOffsetUs);
+        }
         // LACK OF SUPPORT FOR:
-        //    format.containerMimeType;
         //    format.id;
         //    format.metadata;
         //    format.stereoMode;
@@ -2041,6 +2118,12 @@
         expectedTypeByParameterName.put(PARAMETER_EXPOSE_DUMMY_SEEKMAP, Boolean.class);
         expectedTypeByParameterName.put(
                 PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT, Boolean.class);
+        expectedTypeByParameterName.put(
+                PARAMETER_OVERRIDE_IN_BAND_CAPTION_DECLARATIONS, Boolean.class);
+        expectedTypeByParameterName.put(PARAMETER_EXPOSE_EMSG_TRACK, Boolean.class);
+        // We do not check PARAMETER_EXPOSE_CAPTION_FORMATS here, and we do it in setParameters
+        // instead. Checking that the value is a List is insufficient to catch wrong parameter
+        // value types.
         EXPECTED_TYPE_BY_PARAMETER_NAME = Collections.unmodifiableMap(expectedTypeByParameterName);
     }
 }
diff --git a/api/test-current.txt b/api/test-current.txt
index 5bae370..7fd5698 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5264,6 +5264,7 @@
     ctor public AutofillId(int, int);
     ctor public AutofillId(@NonNull android.view.autofill.AutofillId, long, int);
     method public boolean equalsIgnoreSession(@Nullable android.view.autofill.AutofillId);
+    method @NonNull public static android.view.autofill.AutofillId withoutSession(@NonNull android.view.autofill.AutofillId);
   }
 
   public final class AutofillManager {
@@ -5505,6 +5506,19 @@
 
 }
 
+package android.widget.inline {
+
+  public class InlineContentView extends android.view.ViewGroup {
+    method public void setChildSurfacePackageUpdater(@Nullable android.widget.inline.InlineContentView.SurfacePackageUpdater);
+  }
+
+  public static interface InlineContentView.SurfacePackageUpdater {
+    method public void getSurfacePackage(@NonNull java.util.function.Consumer<android.view.SurfaceControlViewHost.SurfacePackage>);
+    method public void onSurfacePackageReleased();
+  }
+
+}
+
 package android.window {
 
   public final class DisplayAreaInfo implements android.os.Parcelable {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index a5f0ac9..ca03343 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -6131,6 +6131,10 @@
  */
 message ProcStats {
     optional ProcessStatsSectionProto proc_stats_section = 1;
+    // Data pulled from device into this is sometimes sharded across multiple atoms to work around
+    // a size limit. When this happens, this shard ID will contain an increasing 1-indexed integer
+    // with the number of this shard.
+    optional int32 shard_id = 2;
 }
 
 /**
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 6f8233d..c9031b7 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -763,23 +763,24 @@
 
     @Override
     public void revokeRuntimePermission(String packageName, String permName, UserHandle user) {
-        if (DEBUG_TRACE_PERMISSION_UPDATES
-                && shouldTraceGrant(packageName, permName, user.getIdentifier())) {
-            Log.i(TAG, "App " + mContext.getPackageName() + " is revoking " + packageName + " "
-                    + permName + " for user " + user.getIdentifier(), new RuntimeException());
-        }
-        try {
-            mPermissionManager
-                    .revokeRuntimePermission(packageName, permName, user.getIdentifier());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        revokeRuntimePermission(packageName, permName, user, null);
     }
 
     @Override
     public void revokeRuntimePermission(String packageName, String permName, UserHandle user,
             String reason) {
-        // TODO evanseverson: impl
+        if (DEBUG_TRACE_PERMISSION_UPDATES
+                && shouldTraceGrant(packageName, permName, user.getIdentifier())) {
+            Log.i(TAG, "App " + mContext.getPackageName() + " is revoking " + packageName + " "
+                    + permName + " for user " + user.getIdentifier() + " with reason " + reason,
+                    new RuntimeException());
+        }
+        try {
+            mPermissionManager
+                    .revokeRuntimePermission(packageName, permName, user.getIdentifier(), reason);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     @Override
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 86a3579..a828aac 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1900,11 +1900,13 @@
 
     @Override
     public Object getSystemService(String name) {
+        // We may override this API from outer context.
+        final boolean isUiContext = isUiContext() || getOuterContext().isUiContext();
         // Check incorrect Context usage.
-        if (isUiComponent(name) && !isUiContext() && vmIncorrectContextUseEnabled()) {
+        if (isUiComponent(name) && !isUiContext && vmIncorrectContextUseEnabled()) {
             final String errorMessage = "Tried to access visual service "
                     + SystemServiceRegistry.getSystemServiceClassName(name)
-                    + " from a non-visual Context. ";
+                    + " from a non-visual Context:" + getOuterContext();
             final String message = "Visual services, such as WindowManager, WallpaperService or "
                     + "LayoutInflater should be accessed from Activity or other visual Context. "
                     + "Use an Activity or a Context created with "
@@ -2369,6 +2371,7 @@
         context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId,
                 overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo(),
                 mResources.getLoaders()));
+        context.mIsUiContext = isUiContext() || getOuterContext().isUiContext();
         return context;
     }
 
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index e84c5e5..9459577 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -677,4 +677,10 @@
      * Return whether the app freezer is supported (true) or not (false) by this system.
      */
     boolean isAppFreezerSupported();
+
+
+    /**
+     * Kills uid with the reason of permission change.
+     */
+    void killUidForPermissionChange(int appId, int userId, String reason);
 }
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 82e9881..ce51dba 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -294,7 +294,7 @@
         }
         final long identity = Binder.clearCallingIdentity();
         try {
-            mPermissionManager.revokeRuntimePermission(packageName, permission, userId);
+            mPermissionManager.revokeRuntimePermission(packageName, permission, userId, null);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 30ee326..15625cd 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -680,7 +680,7 @@
      * </table><br>
      * </p>
      *
-     *<p>Devices capable of streaming concurrently with other devices as described by
+     *<p>BACKWARD_COMPATIBLE devices capable of streaming concurrently with other devices as described by
      * {@link android.hardware.camera2.CameraManager#getConcurrentCameraIds} have the
      * following guaranteed streams (when streaming concurrently with other devices)</p>
      *
@@ -696,10 +696,14 @@
      * </table><br>
      * </p>
      *
+     * <p> Devices which are not backwards-compatible, support a mandatory single stream of size sVGA with image format {@code DEPTH16} during concurrent operation.
+     *
      * <p> For guaranteed concurrent stream configurations:</p>
-     * <p> s720p refers to the camera device's resolution for that format from {@link StreamConfigurationMap#getOutputSizes} or
+     * <p> sVGA refers to the camera device's maximum resolution for that format from {@link StreamConfigurationMap#getOutputSizes} or
+     * VGA resolution (640X480) whichever is lower. </p>
+     * <p> s720p refers to the camera device's maximum resolution for that format from {@link StreamConfigurationMap#getOutputSizes} or
      * 720p(1280X720) whichever is lower. </p>
-     * <p> s1440p refers to the camera device's resolution for that format from {@link StreamConfigurationMap#getOutputSizes} or
+     * <p> s1440p refers to the camera device's maximum resolution for that format from {@link StreamConfigurationMap#getOutputSizes} or
      * 1440p(1920X1440) whichever is lower. </p>
      * <p>MONOCHROME-capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES}
      * includes {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME MONOCHROME}) devices
@@ -707,6 +711,7 @@
      * streams with {@code Y8} in all guaranteed stream combinations for the device's hardware level
      * and capabilities.</p>
      *
+     *
      * <p>Devices capable of outputting HEIC formats ({@link StreamConfigurationMap#getOutputFormats}
      * contains {@link android.graphics.ImageFormat#HEIC}) will support substituting {@code JPEG}
      * streams with {@code HEIC} in all guaranteed stream combinations for the device's hardware
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index 20d9c30..776d155 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -685,6 +685,12 @@
                 "Standard still image capture"),
     };
 
+    private static StreamCombinationTemplate sConcurrentDepthOnlyStreamCombinations[] = {
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.DEPTH16, SizeThreshold.VGA) },
+                "Depth capture for mesh based object rendering"),
+    };
+
     /**
      * Helper builder class to generate a list of available mandatory stream combinations.
      * @hide
@@ -729,19 +735,21 @@
                 getAvailableMandatoryConcurrentStreamCombinations() {
             // Since concurrent streaming support is optional, we mandate these stream
             // combinations regardless of camera device capabilities.
+
+            StreamCombinationTemplate []chosenStreamCombinations = sConcurrentStreamCombinations;
             if (!isColorOutputSupported()) {
-                Log.v(TAG, "Device is not backward compatible!");
-                throw new IllegalArgumentException("Camera device which is not BACKWARD_COMPATIBLE"
-                         + " cannot have mandatory concurrent streams");
+                Log.v(TAG, "Device is not backward compatible, depth streams are mandatory!");
+                chosenStreamCombinations = sConcurrentDepthOnlyStreamCombinations;
             }
+            Size sizeVGAp = new Size(640, 480);
             Size size720p = new Size(1280, 720);
             Size size1440p = new Size(1920, 1440);
 
             ArrayList<MandatoryStreamCombination> availableConcurrentStreamCombinations =
                     new ArrayList<MandatoryStreamCombination>();
             availableConcurrentStreamCombinations.ensureCapacity(
-                    sConcurrentStreamCombinations.length);
-            for (StreamCombinationTemplate combTemplate : sConcurrentStreamCombinations) {
+                    chosenStreamCombinations.length);
+            for (StreamCombinationTemplate combTemplate : chosenStreamCombinations) {
                 ArrayList<MandatoryStreamInformation> streamsInfo =
                         new ArrayList<MandatoryStreamInformation>();
                 streamsInfo.ensureCapacity(combTemplate.mStreamTemplates.length);
@@ -753,6 +761,9 @@
                         case s1440p:
                             formatSize = size1440p;
                             break;
+                        case VGA:
+                            formatSize = sizeVGAp;
+                            break;
                         default:
                             formatSize = size720p;
                     }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index d8b1f41..5647bf9 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -471,6 +471,10 @@
 
     final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
         onComputeInsets(mTmpInsets);
+        if (!mViewsCreated) {
+            // The IME views are not ready, keep visible insets untouched.
+            mTmpInsets.visibleTopInsets = 0;
+        }
         if (isExtractViewShown()) {
             // In true fullscreen mode, we just say the window isn't covering
             // any content so we don't impact whatever is behind.
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index ef2a8a1..b36aeb8 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -133,12 +133,23 @@
      * <a href="/training/articles/security-key-attestation.html">key attestation</a> to obtain
      * proof of the device's original identifiers.
      *
-     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
-     * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
-     * privileges (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}). The profile
-     * owner is an app that owns a managed profile on the device; for more details see <a
-     * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
-     * access is deprecated and will be removed in a future release.
+     * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
+     * restrictions, and apps are recommended to use resettable identifiers (see <a
+     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
+     * the following requirements is met:
+     * <ul>
+     *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
+     *     is a privileged permission that can only be granted to apps preloaded on the device.
+     *     <li>If the calling app is the device or profile owner and has been granted the
+     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
+     *     owns a managed profile on the device; for more details see <a
+     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app has carrier privileges (see {@link
+     *     android.telephony.TelephonyManager#hasCarrierPrivileges}) on any active subscription.
+     *     <li>If the calling app is the default SMS role holder (see {@link
+     *     android.app.role.RoleManager#isRoleHeld(String)}).
+     * </ul>
      *
      * <p>If the calling app does not meet one of these requirements then this method will behave
      * as follows:
@@ -150,7 +161,7 @@
      *     the READ_PHONE_STATE permission, or if the calling app is targeting API level 29 or
      *     higher, then a SecurityException is thrown.</li>
      * </ul>
-     * *
+     *
      * @return The serial number if specified.
      */
     @SuppressAutoDoc // No support for device / profile owner.
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 235b083..e231021 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -71,7 +71,7 @@
 
     void grantRuntimePermission(String packageName, String permName, int userId);
 
-    void revokeRuntimePermission(String packageName, String permName, int userId);
+    void revokeRuntimePermission(String packageName, String permName, int userId, String reason);
 
     void resetRuntimePermissions();
 
diff --git a/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl b/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl
index bf0bb9e..7cd372f 100644
--- a/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl
+++ b/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl
@@ -29,6 +29,12 @@
 oneway interface IInlineSuggestionRenderService {
     void renderSuggestion(in IInlineSuggestionUiCallback callback,
                           in InlinePresentation presentation, int width, int height,
-                          in IBinder hostInputToken, int displayId);
+                          in IBinder hostInputToken, int displayId, int userId, int sessionId);
     void getInlineSuggestionsRendererInfo(in RemoteCallback callback);
+
+    /**
+     * Releases the inline suggestion SurfaceControlViewHosts hosted in the service, for the
+     * provided userId and sessionId.
+     */
+    void destroySuggestionViews(int userId, int sessionId);
 }
diff --git a/core/java/android/service/autofill/InlineSuggestionRenderService.java b/core/java/android/service/autofill/InlineSuggestionRenderService.java
index 8790fb2..839caff 100644
--- a/core/java/android/service/autofill/InlineSuggestionRenderService.java
+++ b/core/java/android/service/autofill/InlineSuggestionRenderService.java
@@ -41,6 +41,8 @@
 import android.view.ViewGroup;
 import android.view.WindowManager;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 
 /**
@@ -82,7 +84,7 @@
                         Boolean newValue) {
                     if (evicted) {
                         Log.w(TAG,
-                                "Hit max=100 entries in the cache. Releasing oldest one to make "
+                                "Hit max=30 entries in the cache. Releasing oldest one to make "
                                         + "space.");
                         key.releaseSurfaceControlViewHost();
                     }
@@ -130,7 +132,7 @@
 
     private void handleRenderSuggestion(IInlineSuggestionUiCallback callback,
             InlinePresentation presentation, int width, int height, IBinder hostInputToken,
-            int displayId) {
+            int displayId, int userId, int sessionId) {
         if (hostInputToken == null) {
             try {
                 callback.onError();
@@ -192,7 +194,8 @@
                 }
                 return true;
             });
-            final InlineSuggestionUiImpl uiImpl = new InlineSuggestionUiImpl(host, mMainHandler);
+            final InlineSuggestionUiImpl uiImpl = new InlineSuggestionUiImpl(host, mMainHandler,
+                    userId, sessionId);
             mActiveInlineSuggestions.put(uiImpl, true);
 
             // We post the callback invocation to the end of the main thread handler queue, to make
@@ -218,6 +221,18 @@
         callback.sendResult(rendererInfo);
     }
 
+    private void handleDestroySuggestionViews(int userId, int sessionId) {
+        Log.v(TAG, "handleDestroySuggestionViews called for " + userId + ":" + sessionId);
+        for (final InlineSuggestionUiImpl inlineSuggestionUi :
+                mActiveInlineSuggestions.snapshot().keySet()) {
+            if (inlineSuggestionUi.mUserId == userId
+                    && inlineSuggestionUi.mSessionId == sessionId) {
+                Log.v(TAG, "Destroy " + inlineSuggestionUi);
+                inlineSuggestionUi.releaseSurfaceControlViewHost();
+            }
+        }
+    }
+
     /**
      * A wrapper class around the {@link InlineSuggestionUiImpl} to ensure it's not strongly
      * reference by the remote system server process.
@@ -260,10 +275,15 @@
         private SurfaceControlViewHost mViewHost;
         @NonNull
         private final Handler mHandler;
+        private final int mUserId;
+        private final int mSessionId;
 
-        InlineSuggestionUiImpl(SurfaceControlViewHost viewHost, Handler handler) {
+        InlineSuggestionUiImpl(SurfaceControlViewHost viewHost, Handler handler, int userId,
+                int sessionId) {
             this.mViewHost = viewHost;
             this.mHandler = handler;
+            this.mUserId = userId;
+            this.mSessionId = sessionId;
         }
 
         /**
@@ -302,6 +322,16 @@
         }
     }
 
+    /** @hide */
+    @Override
+    protected final void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
+            @NonNull String[] args) {
+        pw.println("mActiveInlineSuggestions: " + mActiveInlineSuggestions.size());
+        for (InlineSuggestionUiImpl impl : mActiveInlineSuggestions.snapshot().keySet()) {
+            pw.printf("ui: [%s] - [%d]  [%d]\n", impl, impl.mUserId, impl.mSessionId);
+        }
+    }
+
     @Override
     @Nullable
     public final IBinder onBind(@NonNull Intent intent) {
@@ -311,11 +341,12 @@
                 @Override
                 public void renderSuggestion(@NonNull IInlineSuggestionUiCallback callback,
                         @NonNull InlinePresentation presentation, int width, int height,
-                        @Nullable IBinder hostInputToken, int displayId) {
+                        @Nullable IBinder hostInputToken, int displayId, int userId,
+                        int sessionId) {
                     mMainHandler.sendMessage(
                             obtainMessage(InlineSuggestionRenderService::handleRenderSuggestion,
                                     InlineSuggestionRenderService.this, callback, presentation,
-                                    width, height, hostInputToken, displayId));
+                                    width, height, hostInputToken, displayId, userId, sessionId));
                 }
 
                 @Override
@@ -324,6 +355,12 @@
                             InlineSuggestionRenderService::handleGetInlineSuggestionsRendererInfo,
                             InlineSuggestionRenderService.this, callback));
                 }
+                @Override
+                public void destroySuggestionViews(int userId, int sessionId) {
+                    mMainHandler.sendMessage(obtainMessage(
+                            InlineSuggestionRenderService::handleDestroySuggestionViews,
+                            InlineSuggestionRenderService.this, userId, sessionId));
+                }
             }.asBinder();
         }
 
diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java
index 4e5aa00..6bd376a 100644
--- a/core/java/android/service/controls/ControlsProviderService.java
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -296,6 +296,10 @@
 
     /**
      * Request SystemUI to prompt the user to add a control to favorites.
+     * <br>
+     * SystemUI may not honor this request in some cases, for example if the requested
+     * {@link Control} is already a favorite, or the requesting package is not currently in the
+     * foreground.
      *
      * @param context A context
      * @param componentName Component name of the {@link ControlsProviderService}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index a3c88ba..c6be91f 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -514,6 +514,12 @@
     /** Set of inset types for which an animation was started since last resetting this field */
     private @InsetsType int mLastStartedAnimTypes;
 
+    /** Set of inset types which cannot be controlled by the user animation */
+    private @InsetsType int mDisabledUserAnimationInsetsTypes;
+
+    private Runnable mInvokeControllableInsetsChangedListeners =
+            this::invokeControllableInsetsChangedListeners;
+
     public InsetsController(Host host) {
         this(host, (controller, type) -> {
             if (type == ITYPE_IME) {
@@ -628,22 +634,57 @@
 
     private void updateState(InsetsState newState) {
         mState.setDisplayFrame(newState.getDisplayFrame());
-        for (int i = 0; i < InsetsState.SIZE; i++) {
-            InsetsSource source = newState.peekSource(i);
-            if (source == null) continue;;
-            getSourceConsumer(source.getType()).updateSource(source);
-        }
-        for (int i = 0; i < InsetsState.SIZE; i++) {
-            InsetsSource source = mState.peekSource(i);
+        @InsetsType int disabledUserAnimationTypes = 0;
+        @InsetsType int[] cancelledUserAnimationTypes = {0};
+        for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
+            InsetsSource source = newState.peekSource(type);
             if (source == null) continue;
-            if (newState.peekSource(source.getType()) == null) {
-                mState.removeSource(source.getType());
+            @AnimationType int animationType = getAnimationType(type);
+            if (!source.isUserControllable()) {
+                @InsetsType int insetsType = toPublicType(type);
+                // The user animation is not allowed when visible frame is empty.
+                disabledUserAnimationTypes |= insetsType;
+                if (animationType == ANIMATION_TYPE_USER) {
+                    // Existing user animation needs to be cancelled.
+                    animationType = ANIMATION_TYPE_NONE;
+                    cancelledUserAnimationTypes[0] |= insetsType;
+                }
+            }
+            getSourceConsumer(type).updateSource(source, animationType);
+        }
+        for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
+            InsetsSource source = mState.peekSource(type);
+            if (source == null) continue;
+            if (newState.peekSource(type) == null) {
+                mState.removeSource(type);
             }
         }
         if (mCaptionInsetsHeight != 0) {
             mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(mFrame.left, mFrame.top,
                     mFrame.right, mFrame.top + mCaptionInsetsHeight));
         }
+
+        updateDisabledUserAnimationTypes(disabledUserAnimationTypes);
+
+        if (cancelledUserAnimationTypes[0] != 0) {
+            mHandler.post(() -> show(cancelledUserAnimationTypes[0]));
+        }
+    }
+
+    private void updateDisabledUserAnimationTypes(@InsetsType int disabledUserAnimationTypes) {
+        @InsetsType int diff = mDisabledUserAnimationInsetsTypes ^ disabledUserAnimationTypes;
+        if (diff != 0) {
+            for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
+                InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
+                if (consumer.getControl() != null
+                        && (toPublicType(consumer.getType()) & diff) != 0) {
+                    mHandler.removeCallbacks(mInvokeControllableInsetsChangedListeners);
+                    mHandler.post(mInvokeControllableInsetsChangedListeners);
+                    break;
+                }
+            }
+            mDisabledUserAnimationInsetsTypes = disabledUserAnimationTypes;
+        }
     }
 
     private boolean captionInsetsUnchanged() {
@@ -847,6 +888,18 @@
                     + " while an existing " + Type.toString(mTypesBeingCancelled)
                     + " is being cancelled.");
         }
+        if (animationType == ANIMATION_TYPE_USER) {
+            final @InsetsType int disabledTypes = types & mDisabledUserAnimationInsetsTypes;
+            if (DEBUG) Log.d(TAG, "user animation disabled types: " + disabledTypes);
+            types &= ~mDisabledUserAnimationInsetsTypes;
+
+            if (fromIme && (disabledTypes & ime()) != 0
+                    && !mState.getSource(ITYPE_IME).isVisible()) {
+                // We've requested IMM to show IME, but the IME is not controllable. We need to
+                // cancel the request.
+                getSourceConsumer(ITYPE_IME).hide(true, animationType);
+            }
+        }
         if (types == 0) {
             // nothing to animate.
             listener.onCancelled(null);
@@ -1320,7 +1373,8 @@
         @InsetsType int result = 0;
         for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
             InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
-            if (consumer.getControl() != null) {
+            InsetsSource source = mState.peekSource(consumer.mType);
+            if (consumer.getControl() != null && source != null && source.isUserControllable()) {
                 result |= toPublicType(consumer.mType);
             }
         }
@@ -1331,6 +1385,7 @@
      * @return The types that are now animating due to a listener invoking control/show/hide
      */
     private @InsetsType int invokeControllableInsetsChangedListeners() {
+        mHandler.removeCallbacks(mInvokeControllableInsetsChangedListeners);
         mLastStartedAnimTypes = 0;
         @InsetsType int types = calculateControllableTypes();
         int size = mControllableInsetsChangedListeners.size();
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 15b9a93..dbf7570 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -92,6 +92,11 @@
         return mVisible;
     }
 
+    boolean isUserControllable() {
+        // If mVisibleFrame is null, it will be the same area as mFrame.
+        return mVisibleFrame == null || !mVisibleFrame.isEmpty();
+    }
+
     /**
      * Calculates the insets this source will cause to a client window.
      *
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index b62e67c..40e6f4b 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -18,8 +18,8 @@
 
 import static android.view.InsetsController.ANIMATION_TYPE_NONE;
 import static android.view.InsetsController.AnimationType;
-import static android.view.InsetsState.getDefaultVisibility;
 import static android.view.InsetsController.DEBUG;
+import static android.view.InsetsState.getDefaultVisibility;
 import static android.view.InsetsState.toPublicType;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
@@ -284,9 +284,9 @@
     }
 
     @VisibleForTesting(visibility = PACKAGE)
-    public void updateSource(InsetsSource newSource) {
+    public void updateSource(InsetsSource newSource, @AnimationType int animationType) {
         InsetsSource source = mState.peekSource(mType);
-        if (source == null || mController.getAnimationType(mType) == ANIMATION_TYPE_NONE
+        if (source == null || animationType == ANIMATION_TYPE_NONE
                 || source.getFrame().equals(newSource.getFrame())) {
             mPendingFrame = null;
             mPendingVisibleFrame = null;
@@ -295,7 +295,7 @@
         }
 
         // Frame is changing while animating. Keep note of the new frame but keep existing frame
-        // until animaition is finished.
+        // until animation is finished.
         newSource = new InsetsSource(newSource);
         mPendingFrame = new Rect(newSource.getFrame());
         mPendingVisibleFrame = newSource.getVisibleFrame() != null
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 19eff72..51b0c6b 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -487,6 +487,21 @@
     public static final int FLAG_TAINTED = 0x80000000;
 
     /**
+     * Private flag indicating that this event was synthesized by the system and should be delivered
+     * to the accessibility focused view first. When being dispatched such an event is not handled
+     * by predecessors of the accessibility focused view and after the event reaches that view the
+     * flag is cleared and normal event dispatch is performed. This ensures that the platform can
+     * click on any view that has accessibility focus which is semantically equivalent to asking the
+     * view to perform a click accessibility action but more generic as views not implementing click
+     * action correctly can still be activated.
+     *
+     * @hide
+     * @see #isTargetAccessibilityFocus()
+     * @see #setTargetAccessibilityFocus(boolean)
+     */
+    public static final int FLAG_TARGET_ACCESSIBILITY_FOCUS = 0x40000000;
+
+    /**
      * Flag indicating the motion event intersected the top edge of the screen.
      */
     public static final int EDGE_TOP = 0x00000001;
@@ -2140,6 +2155,20 @@
     }
 
     /** @hide */
+    public  boolean isTargetAccessibilityFocus() {
+        final int flags = getFlags();
+        return (flags & FLAG_TARGET_ACCESSIBILITY_FOCUS) != 0;
+    }
+
+    /** @hide */
+    public void setTargetAccessibilityFocus(boolean targetsFocus) {
+        final int flags = getFlags();
+        nativeSetFlags(mNativePtr, targetsFocus
+                ? flags | FLAG_TARGET_ACCESSIBILITY_FOCUS
+                : flags & ~FLAG_TARGET_ACCESSIBILITY_FOCUS);
+    }
+
+    /** @hide */
     public final boolean isHoverExitPending() {
         final int flags = getFlags();
         return (flags & FLAG_HOVER_EXIT_PENDING) != 0;
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 3b38365..daeb1c9 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -63,8 +63,10 @@
 import libcore.util.NativeAllocationRegistry;
 
 import java.io.Closeable;
+import java.lang.ref.WeakReference;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.util.ArrayList;
 import java.util.Objects;
 
 /**
@@ -226,24 +228,86 @@
     private static native void nativeSetFixedTransformHint(long transactionObj, long nativeObject,
             int transformHint);
 
+    @Nullable
+    @GuardedBy("mLock")
+    private ArrayList<OnReparentListener> mReparentListeners;
+
+    /**
+     * Listener to observe surface reparenting.
+     *
+     * @hide
+     */
+    public interface OnReparentListener {
+
+        /**
+         * Callback for reparenting surfaces.
+         *
+         * Important: You should only interact with the provided surface control
+         * only if you have a contract with its owner to avoid them closing it
+         * under you or vise versa.
+         *
+         * @param transaction The transaction that would commit reparenting.
+         * @param parent The future parent surface.
+         */
+        void onReparent(@NonNull Transaction transaction, @Nullable SurfaceControl parent);
+    }
+
     private final CloseGuard mCloseGuard = CloseGuard.get();
     private String mName;
-    /**
+
+     /**
      * @hide
      */
     public long mNativeObject;
     private long mNativeHandle;
 
-    // TODO: Move this to native.
-    private final Object mSizeLock = new Object();
-    @GuardedBy("mSizeLock")
+    // TODO: Move width/height to native and fix locking through out.
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
     private int mWidth;
-    @GuardedBy("mSizeLock")
+    @GuardedBy("mLock")
     private int mHeight;
 
+    private WeakReference<View> mLocalOwnerView;
+
     static Transaction sGlobalTransaction;
     static long sTransactionNestCount = 0;
 
+    /**
+     * Adds a reparenting listener.
+     *
+     * @param listener The listener.
+     * @return Whether listener was added.
+     *
+     * @hide
+     */
+    public boolean addOnReparentListener(@NonNull OnReparentListener listener) {
+        synchronized (mLock) {
+            if (mReparentListeners == null) {
+                mReparentListeners = new ArrayList<>(1);
+            }
+            return mReparentListeners.add(listener);
+        }
+    }
+
+    /**
+     * Removes a reparenting listener.
+     *
+     * @param listener The listener.
+     * @return Whether listener was removed.
+     *
+     * @hide
+     */
+    public boolean removeOnReparentListener(@NonNull OnReparentListener listener) {
+        synchronized (mLock) {
+            final boolean removed = mReparentListeners.remove(listener);
+            if (mReparentListeners.isEmpty()) {
+                mReparentListeners = null;
+            }
+            return removed;
+        }
+    }
+
     /* flags used in constructor (keep in sync with ISurfaceComposerClient.h) */
 
     /**
@@ -455,6 +519,7 @@
         mName = other.mName;
         mWidth = other.mWidth;
         mHeight = other.mHeight;
+        mLocalOwnerView = other.mLocalOwnerView;
         assignNativeObject(nativeCopyFromSurfaceControl(other.mNativeObject));
     }
 
@@ -553,6 +618,7 @@
         private int mHeight;
         private int mFormat = PixelFormat.OPAQUE;
         private String mName;
+        private WeakReference<View> mLocalOwnerView;
         private SurfaceControl mParent;
         private SparseIntArray mMetadata;
 
@@ -587,7 +653,8 @@
                         "Only buffer layers can set a valid buffer size.");
             }
             return new SurfaceControl(
-                    mSession, mName, mWidth, mHeight, mFormat, mFlags, mParent, mMetadata);
+                    mSession, mName, mWidth, mHeight, mFormat, mFlags, mParent, mMetadata,
+                    mLocalOwnerView);
         }
 
         /**
@@ -602,6 +669,27 @@
         }
 
         /**
+         * Set the local owner view for the surface. This view is only
+         * valid in the same process and is not transferred in an IPC.
+         *
+         * Note: This is used for cases where we want to know the view
+         * that manages the surface control while intercepting reparenting.
+         * A specific example is InlineContentView which exposes is surface
+         * control for reparenting as a way to implement clipping of several
+         * InlineContentView instances within a certain area.
+         *
+         * @param view The owner view.
+         * @return This builder.
+         *
+         * @hide
+         */
+        @NonNull
+        public Builder setLocalOwnerView(@NonNull View view) {
+            mLocalOwnerView = new WeakReference<>(view);
+            return this;
+        }
+
+        /**
          * Set the initial size of the controlled surface's buffers in pixels.
          *
          * @param width The buffer width in pixels.
@@ -858,7 +946,7 @@
      * @throws throws OutOfResourcesException If the SurfaceControl cannot be created.
      */
     private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
-            SurfaceControl parent, SparseIntArray metadata)
+            SurfaceControl parent, SparseIntArray metadata, WeakReference<View> localOwnerView)
                     throws OutOfResourcesException, IllegalArgumentException {
         if (name == null) {
             throw new IllegalArgumentException("name must not be null");
@@ -867,6 +955,7 @@
         mName = name;
         mWidth = w;
         mHeight = h;
+        mLocalOwnerView = localOwnerView;
         Parcel metaParcel = Parcel.obtain();
         try {
             if (metadata != null && metadata.size() > 0) {
@@ -1307,7 +1396,7 @@
      * @hide
      */
     public int getWidth() {
-        synchronized (mSizeLock) {
+        synchronized (mLock) {
             return mWidth;
         }
     }
@@ -1316,11 +1405,22 @@
      * @hide
      */
     public int getHeight() {
-        synchronized (mSizeLock) {
+        synchronized (mLock) {
             return mHeight;
         }
     }
 
+    /**
+     * Gets the local view that owns this surface.
+     *
+     * @return The owner view.
+     *
+     * @hide
+     */
+    public @Nullable View getLocalOwnerView() {
+        return (mLocalOwnerView != null) ? mLocalOwnerView.get() : null;
+    }
+
     @Override
     public String toString() {
         return "Surface(name=" + mName + ")/@0x" +
@@ -2165,6 +2265,9 @@
         public long mNativeObject;
 
         private final ArrayMap<SurfaceControl, Point> mResizedSurfaces = new ArrayMap<>();
+        private final ArrayMap<SurfaceControl, SurfaceControl> mReparentedSurfaces =
+                 new ArrayMap<>();
+
         Runnable mFreeNativeResources;
         private static final float[] INVALID_COLOR = {-1, -1, -1};
 
@@ -2205,6 +2308,8 @@
          */
         @Override
         public void close() {
+            mResizedSurfaces.clear();
+            mReparentedSurfaces.clear();
             mFreeNativeResources.run();
             mNativeObject = 0;
         }
@@ -2215,6 +2320,7 @@
          */
         public void apply(boolean sync) {
             applyResizedSurfaces();
+            notifyReparentedSurfaces();
             nativeApplyTransaction(mNativeObject, sync);
         }
 
@@ -2222,7 +2328,7 @@
             for (int i = mResizedSurfaces.size() - 1; i >= 0; i--) {
                 final Point size = mResizedSurfaces.valueAt(i);
                 final SurfaceControl surfaceControl = mResizedSurfaces.keyAt(i);
-                synchronized (surfaceControl.mSizeLock) {
+                synchronized (surfaceControl.mLock) {
                     surfaceControl.mWidth = size.x;
                     surfaceControl.mHeight = size.y;
                 }
@@ -2230,6 +2336,22 @@
             mResizedSurfaces.clear();
         }
 
+        private void notifyReparentedSurfaces() {
+            final int reparentCount = mReparentedSurfaces.size();
+            for (int i = reparentCount - 1; i >= 0; i--) {
+                final SurfaceControl child = mReparentedSurfaces.keyAt(i);
+                synchronized (child.mLock) {
+                    final int listenerCount = (child.mReparentListeners != null)
+                            ? child.mReparentListeners.size() : 0;
+                    for (int j = 0; j < listenerCount; j++) {
+                        final OnReparentListener listener = child.mReparentListeners.get(j);
+                        listener.onReparent(this, mReparentedSurfaces.valueAt(i));
+                    }
+                    mReparentedSurfaces.removeAt(i);
+                }
+            }
+        }
+
         /**
          * Toggle the visibility of a given Layer and it's sub-tree.
          *
@@ -2632,6 +2754,7 @@
                 otherObject = newParent.mNativeObject;
             }
             nativeReparent(mNativeObject, sc.mNativeObject, otherObject);
+            mReparentedSurfaces.put(sc, newParent);
             return this;
         }
 
@@ -2912,6 +3035,8 @@
             }
             mResizedSurfaces.putAll(other.mResizedSurfaces);
             other.mResizedSurfaces.clear();
+            mReparentedSurfaces.putAll(other.mReparentedSurfaces);
+            other.mReparentedSurfaces.clear();
             nativeMergeTransaction(mNativeObject, other.mNativeObject);
             return this;
         }
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 90e1eab..0d21eb5 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -44,7 +44,6 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.SurfaceControl.Transaction;
-import android.view.SurfaceControlViewHost;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.IAccessibilityEmbeddedConnection;
 
@@ -988,6 +987,7 @@
 
                     mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
                         .setName(name)
+                        .setLocalOwnerView(this)
                         .setOpaque((mSurfaceFlags & SurfaceControl.OPAQUE) != 0)
                         .setBufferSize(mSurfaceWidth, mSurfaceHeight)
                         .setFormat(mFormat)
@@ -996,6 +996,7 @@
                         .build();
                     mBackgroundControl = new SurfaceControl.Builder(mSurfaceSession)
                         .setName("Background for -" + name)
+                        .setLocalOwnerView(this)
                         .setOpaque(true)
                         .setColorLayer()
                         .setParent(mSurfaceControl)
@@ -1051,11 +1052,12 @@
                     // we still need to latch a buffer).
                     // b/28866173
                     if (sizeChanged || creating || !mRtHandlingPositionUpdates) {
-                        mTmpTransaction.setPosition(mSurfaceControl, mScreenRect.left,
-                                mScreenRect.top);
-                        mTmpTransaction.setMatrix(mSurfaceControl,
-                                mScreenRect.width() / (float) mSurfaceWidth, 0.0f, 0.0f,
-                                mScreenRect.height() / (float) mSurfaceHeight);
+                        onSetSurfacePositionAndScaleRT(mTmpTransaction, mSurfaceControl,
+                                mScreenRect.left, /*positionLeft*/
+                                mScreenRect.top /*positionTop*/ ,
+                                mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
+                                mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
+
                         // Set a window crop when creating the surface or changing its size to
                         // crop the buffer to the surface size since the buffer producer may
                         // use SCALING_MODE_SCALE and submit a larger size than the surface
@@ -1211,6 +1213,40 @@
             Surface viewRootSurface, long nextViewRootFrameNumber) {
     }
 
+    /**
+     * Sets the surface position and scale. Can be called on
+     * the UI thread as well as on the renderer thread.
+     *
+     * @param transaction Transaction in which to execute.
+     * @param surface Surface whose location to set.
+     * @param positionLeft The left position to set.
+     * @param positionTop The top position to set.
+     * @param postScaleX The X axis post scale
+     * @param postScaleY The Y axis post scale
+     *
+     * @hide
+     */
+    protected void onSetSurfacePositionAndScaleRT(@NonNull Transaction transaction,
+            @NonNull SurfaceControl surface, int positionLeft, int positionTop,
+            float postScaleX, float postScaleY) {
+        transaction.setPosition(surface, positionLeft, positionTop);
+        transaction.setMatrix(surface, postScaleX /*dsdx*/, 0f /*dtdx*/,
+                0f /*dtdy*/, postScaleY /*dsdy*/);
+    }
+
+    /** @hide */
+    public void requestUpdateSurfacePositionAndScale() {
+        if (mSurfaceControl == null) {
+            return;
+        }
+        onSetSurfacePositionAndScaleRT(mTmpTransaction, mSurfaceControl,
+                mScreenRect.left, /*positionLeft*/
+                mScreenRect.top/*positionTop*/ ,
+                mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
+                mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
+        mTmpTransaction.apply();
+    }
+
     private void applySurfaceTransforms(SurfaceControl surface, SurfaceControl.Transaction t,
             Rect position, long frameNumber) {
         final ViewRootImpl viewRoot = getViewRootImpl();
@@ -1219,16 +1255,26 @@
                     frameNumber);
         }
 
-        t.setPosition(surface, position.left, position.top);
-        t.setMatrix(surface,
-                position.width() / (float) mSurfaceWidth,
-                0.0f, 0.0f,
-                position.height() / (float) mSurfaceHeight);
+        onSetSurfacePositionAndScaleRT(t, surface,
+                position.left /*positionLeft*/,
+                position.top /*positionTop*/,
+                position.width() / (float) mSurfaceWidth /*postScaleX*/,
+                position.height() / (float) mSurfaceHeight /*postScaleY*/);
+
         if (mViewVisibility) {
             t.show(surface);
         }
     }
 
+    /**
+     * @return The last render position of the backing surface or an empty rect.
+     *
+     * @hide
+     */
+    public @NonNull Rect getSurfaceRenderPosition() {
+        return mRTLastReportedPosition;
+    }
+
     private void setParentSpaceRectangle(Rect position, long frameNumber) {
         final ViewRootImpl viewRoot = getViewRootImpl();
         final boolean useBLAST = viewRoot.isDrawingToBLASTTransaction();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1226202..df1c672 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -14274,6 +14274,14 @@
      */
     public boolean dispatchTouchEvent(MotionEvent event) {
         // If the event should be handled by accessibility focus first.
+        if (event.isTargetAccessibilityFocus()) {
+            // We don't have focus or no virtual descendant has it, do not handle the event.
+            if (!isAccessibilityFocusedViewOrHost()) {
+                return false;
+            }
+            // We have focus and got the event, then use normal event dispatch.
+            event.setTargetAccessibilityFocus(false);
+        }
         boolean result = false;
 
         if (mInputEventConsistencyVerifier != null) {
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 0d2d4d1..ffeeb80 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -500,12 +500,13 @@
      */
     public static ViewConfiguration get(Context context) {
         if (!context.isUiContext() && vmIncorrectContextUseEnabled()) {
-            final String errorMessage = "Tried to access UI constants from a non-visual Context.";
+            final String errorMessage = "Tried to access UI constants from a non-visual Context:"
+                    + context;
             final String message = "UI constants, such as display metrics or window metrics, "
                     + "must be accessed from Activity or other visual Context. "
                     + "Use an Activity or a Context created with "
                     + "Context#createWindowContext(int, Bundle), which are adjusted to the "
-                    + "configuration and visual bounds of an area on screen.";
+                    + "configuration and visual bounds of an area on screen";
             final Exception exception = new IllegalArgumentException(errorMessage);
             StrictMode.onIncorrectContextUsed(message, exception);
             Log.e(TAG, errorMessage + message, exception);
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index e3362aa..77fedd7 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2048,8 +2048,26 @@
             for (int i = childrenCount - 1; i >= 0; i--) {
                 final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
                 final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
+                View childWithAccessibilityFocus =
+                        event.isTargetAccessibilityFocus()
+                                ? findChildWithAccessibilityFocus()
+                                : null;
+
                 if (!child.canReceivePointerEvents()
                         || !isTransformedTouchPointInView(x, y, child, null)) {
+
+                    // If there is a view that has accessibility focus we want it
+                    // to get the event first and if not handled we will perform a
+                    // normal dispatch. We may do a double iteration but this is
+                    // safer given the timeframe.
+                    if (childWithAccessibilityFocus != null) {
+                        if (childWithAccessibilityFocus != child) {
+                            continue;
+                        }
+                        childWithAccessibilityFocus = null;
+                        i = childrenCount - 1;
+                    }
+                    event.setTargetAccessibilityFocus(false);
                     continue;
                 }
                 final PointerIcon pointerIcon =
@@ -2617,6 +2635,12 @@
             mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
         }
 
+        // If the event targets the accessibility focused view and this is it, start
+        // normal event dispatch. Maybe a descendant is what will handle the click.
+        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
+            ev.setTargetAccessibilityFocus(false);
+        }
+
         boolean handled = false;
         if (onFilterTouchEventForSecurity(ev)) {
             final int action = ev.getAction();
@@ -2647,6 +2671,13 @@
                 // so this view group continues to intercept touches.
                 intercepted = true;
             }
+
+            // If intercepted, start normal event dispatch. Also if there is already
+            // a view that is handling the gesture, do normal event dispatch.
+            if (intercepted || mFirstTouchTarget != null) {
+                ev.setTargetAccessibilityFocus(false);
+            }
+
             // Check for cancelation.
             final boolean canceled = resetCancelNextUpFlag(this)
                     || actionMasked == MotionEvent.ACTION_CANCEL;
@@ -2658,6 +2689,14 @@
             TouchTarget newTouchTarget = null;
             boolean alreadyDispatchedToNewTouchTarget = false;
             if (!canceled && !intercepted) {
+                // If the event is targeting accessibility focus we give it to the
+                // view that has accessibility focus and if it does not handle it
+                // we clear the flag and dispatch the event to all children as usual.
+                // We are looking up the accessibility focused host to avoid keeping
+                // state since these events are very rare.
+                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
+                        ? findChildWithAccessibilityFocus() : null;
+
                 if (actionMasked == MotionEvent.ACTION_DOWN
                         || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                         || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
@@ -2720,6 +2759,10 @@
                                 alreadyDispatchedToNewTouchTarget = true;
                                 break;
                             }
+
+                            // The accessibility focus didn't handle the event, so clear
+                            // the flag and do a normal dispatch to all children.
+                            ev.setTargetAccessibilityFocus(false);
                         }
                         if (preorderedList != null) preorderedList.clear();
                     }
@@ -2803,6 +2846,34 @@
         return buildOrderedChildList();
     }
 
+     /**
+     * Finds the child which has accessibility focus.
+     *
+     * @return The child that has focus.
+     */
+    private View findChildWithAccessibilityFocus() {
+        ViewRootImpl viewRoot = getViewRootImpl();
+        if (viewRoot == null) {
+            return null;
+        }
+
+        View current = viewRoot.getAccessibilityFocusedHost();
+        if (current == null) {
+            return null;
+        }
+
+        ViewParent parent = current.getParent();
+        while (parent instanceof View) {
+            if (parent == this) {
+                return current;
+            }
+            current = (View) parent;
+            parent = current.getParent();
+        }
+
+        return null;
+    }
+
     /**
      * Resets all touch state in preparation for a new cycle.
      */
@@ -3257,9 +3328,10 @@
                 break;
             }
             default:
-                throw new IllegalStateException("descendant focusability must be "
-                        + "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
-                        + "but is " + descendantFocusability);
+                throw new IllegalStateException(
+                        "descendant focusability must be one of FOCUS_BEFORE_DESCENDANTS,"
+                            + " FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS but is "
+                                + descendantFocusability);
         }
         if (result && !isLayoutValid() && ((mPrivateFlags & PFLAG_WANTS_FOCUS) == 0)) {
             mPrivateFlags |= PFLAG_WANTS_FOCUS;
@@ -4925,7 +4997,8 @@
         if (params == null) {
             params = generateDefaultLayoutParams();
             if (params == null) {
-                throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
+                throw new IllegalArgumentException(
+                        "generateDefaultLayoutParams() cannot return null");
             }
         }
         addView(child, index, params);
diff --git a/core/java/android/view/autofill/AutofillId.java b/core/java/android/view/autofill/AutofillId.java
index 68943bf..32b9cf7 100644
--- a/core/java/android/view/autofill/AutofillId.java
+++ b/core/java/android/view/autofill/AutofillId.java
@@ -73,6 +73,8 @@
     }
 
     /** @hide */
+    @NonNull
+    @TestApi
     public static AutofillId withoutSession(@NonNull AutofillId id) {
         final int flags = id.mFlags & ~FLAG_HAS_SESSION;
         final long virtualChildId =
diff --git a/core/java/android/widget/inline/InlineContentView.java b/core/java/android/widget/inline/InlineContentView.java
index 8ca218c..1b666aa 100644
--- a/core/java/android/widget/inline/InlineContentView.java
+++ b/core/java/android/widget/inline/InlineContentView.java
@@ -18,17 +18,22 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.content.Context;
 import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.PointF;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
+import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver.OnPreDrawListener;
+import android.view.ViewTreeObserver;
 
+import java.lang.ref.WeakReference;
 import java.util.function.Consumer;
 
 /**
@@ -87,8 +92,10 @@
      *
      * @hide
      */
+    @TestApi
     public interface SurfacePackageUpdater {
 
+
         /**
          * Called when the previous surface package is released due to view being detached
          * from the window.
@@ -100,14 +107,16 @@
          *
          * @param consumer consumes the updated surface package.
          */
-        void getSurfacePackage(Consumer<SurfaceControlViewHost.SurfacePackage> consumer);
+        void getSurfacePackage(@NonNull Consumer<SurfaceControlViewHost.SurfacePackage> consumer);
     }
 
     @NonNull
     private final SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
         @Override
         public void surfaceCreated(@NonNull SurfaceHolder holder) {
-            mSurfaceControlCallback.onCreated(mSurfaceView.getSurfaceControl());
+            final SurfaceControl surfaceControl = mSurfaceView.getSurfaceControl();
+            surfaceControl.addOnReparentListener(mOnReparentListener);
+            mSurfaceControlCallback.onCreated(surfaceControl);
         }
 
         @Override
@@ -118,7 +127,36 @@
 
         @Override
         public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
-            mSurfaceControlCallback.onDestroyed(mSurfaceView.getSurfaceControl());
+            final SurfaceControl surfaceControl = mSurfaceView.getSurfaceControl();
+            surfaceControl.removeOnReparentListener(mOnReparentListener);
+            mSurfaceControlCallback.onDestroyed(surfaceControl);
+        }
+    };
+
+    @NonNull
+    private final SurfaceControl.OnReparentListener mOnReparentListener =
+            new SurfaceControl.OnReparentListener() {
+                @Override
+                public void onReparent(SurfaceControl.Transaction transaction,
+                        SurfaceControl parent) {
+                    final View parentSurfaceOwnerView = (parent != null)
+                            ? parent.getLocalOwnerView() : null;
+                    if (parentSurfaceOwnerView instanceof SurfaceView) {
+                        mParentSurfaceOwnerView = new WeakReference<>(
+                                (SurfaceView) parentSurfaceOwnerView);
+                    } else {
+                        mParentSurfaceOwnerView = null;
+                    }
+                }
+            };
+
+    @NonNull
+    private final ViewTreeObserver.OnDrawListener mOnDrawListener =
+            new ViewTreeObserver.OnDrawListener() {
+        @Override
+        public void onDraw() {
+            computeParentPositionAndScale();
+            mSurfaceView.setVisibility(VISIBLE);
         }
     };
 
@@ -126,21 +164,20 @@
     private final SurfaceView mSurfaceView;
 
     @Nullable
+    private WeakReference<SurfaceView> mParentSurfaceOwnerView;
+
+    @Nullable
+    private int[] mParentPosition;
+
+    @Nullable
+    private PointF mParentScale;
+
+    @Nullable
     private SurfaceControlCallback mSurfaceControlCallback;
 
     @Nullable
     private SurfacePackageUpdater mSurfacePackageUpdater;
 
-    @NonNull
-    private final OnPreDrawListener mDrawListener = new OnPreDrawListener() {
-        @Override
-        public boolean onPreDraw() {
-            int visibility = InlineContentView.this.isShown() ? VISIBLE : GONE;
-            mSurfaceView.setVisibility(visibility);
-            return true;
-        }
-    };
-
     /**
      * @inheritDoc
      * @hide
@@ -164,6 +201,7 @@
     public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs,
             int defStyleAttr) {
         this(context, attrs, defStyleAttr, 0);
+        mSurfaceView.setEnableSurfaceClipping(true);
     }
 
     /**
@@ -177,6 +215,12 @@
         return mSurfaceView.getSurfaceControl();
     }
 
+    @Override
+    public void setClipBounds(Rect clipBounds) {
+        super.setClipBounds(clipBounds);
+        mSurfaceView.setClipBounds(clipBounds);
+    }
+
     /**
      * @inheritDoc
      * @hide
@@ -184,10 +228,33 @@
     public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mSurfaceView = new SurfaceView(context, attrs, defStyleAttr, defStyleRes);
+        mSurfaceView = new SurfaceView(context, attrs, defStyleAttr, defStyleRes) {
+            @Override
+            protected void onSetSurfacePositionAndScaleRT(
+                    @NonNull SurfaceControl.Transaction transaction,
+                    @NonNull SurfaceControl surface, int positionLeft, int positionTop,
+                    float postScaleX, float postScaleY) {
+                // If we have a parent position, we need to make our coordinates relative
+                // to the parent in the rendering space.
+                if (mParentPosition != null) {
+                    positionLeft = (int) ((positionLeft - mParentPosition[0]) / mParentScale.x);
+                    positionTop = (int) ((positionTop - mParentPosition[1]) / mParentScale.y);
+                }
+
+                // Any scaling done to the parent or its predecessors would be applied
+                // via the surfaces parent -> child relation, so we only propagate any
+                // scaling set on the InlineContentView itself.
+                postScaleX = InlineContentView.this.getScaleX();
+                postScaleY = InlineContentView.this.getScaleY();
+
+                super.onSetSurfacePositionAndScaleRT(transaction, surface, positionLeft,
+                        positionTop, postScaleX, postScaleY);
+            }
+        };
         mSurfaceView.setZOrderOnTop(true);
         mSurfaceView.getHolder().setFormat(PixelFormat.TRANSPARENT);
         addView(mSurfaceView);
+        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
     }
 
     /**
@@ -195,6 +262,7 @@
      *
      * @hide
      */
+    @TestApi
     public void setChildSurfacePackageUpdater(
             @Nullable SurfacePackageUpdater surfacePackageUpdater) {
         mSurfacePackageUpdater = surfacePackageUpdater;
@@ -213,8 +281,9 @@
                         }
                     });
         }
-        mSurfaceView.setVisibility(VISIBLE);
-        getViewTreeObserver().addOnPreDrawListener(mDrawListener);
+
+        mSurfaceView.setVisibility(getVisibility());
+        getViewTreeObserver().addOnDrawListener(mOnDrawListener);
     }
 
     @Override
@@ -224,7 +293,9 @@
         if (mSurfacePackageUpdater != null) {
             mSurfacePackageUpdater.onSurfacePackageReleased();
         }
-        getViewTreeObserver().removeOnPreDrawListener(mDrawListener);
+
+        getViewTreeObserver().removeOnDrawListener(mOnDrawListener);
+        mSurfaceView.setVisibility(View.GONE);
     }
 
     @Override
@@ -271,4 +342,67 @@
     public boolean setZOrderedOnTop(boolean onTop) {
         return mSurfaceView.setZOrderedOnTop(onTop, /*allowDynamicChange*/ true);
     }
+
+
+    private void computeParentPositionAndScale() {
+        boolean contentPositionOrScaleChanged = false;
+
+        // This method can be called on the UI or render thread but for the cases
+        // it is called these threads are not running concurrently, so no need to lock.
+        final SurfaceView parentSurfaceOwnerView = (mParentSurfaceOwnerView != null)
+                ? mParentSurfaceOwnerView.get() : null;
+
+        if (parentSurfaceOwnerView != null) {
+            if (mParentPosition == null) {
+                mParentPosition = new int[2];
+            }
+            final int oldParentPositionX = mParentPosition[0];
+            final int oldParentPositionY = mParentPosition[1];
+            parentSurfaceOwnerView.getLocationInSurface(mParentPosition);
+            if (oldParentPositionX != mParentPosition[0]
+                    || oldParentPositionY != mParentPosition[1]) {
+                contentPositionOrScaleChanged = true;
+            }
+
+            if (mParentScale == null) {
+                mParentScale = new PointF();
+            }
+
+            final float lastParentSurfaceWidth = parentSurfaceOwnerView
+                    .getSurfaceRenderPosition().width();
+            final float oldParentScaleX = mParentScale.x;
+            if (lastParentSurfaceWidth > 0) {
+                mParentScale.x = lastParentSurfaceWidth /
+                        (float) parentSurfaceOwnerView.getWidth();
+            } else {
+                mParentScale.x = 1.0f;
+            }
+            if (!contentPositionOrScaleChanged
+                    && Float.compare(oldParentScaleX, mParentScale.x) != 0) {
+                contentPositionOrScaleChanged = true;
+            }
+
+            final float lastParentSurfaceHeight = parentSurfaceOwnerView
+                    .getSurfaceRenderPosition().height();
+            final float oldParentScaleY = mParentScale.y;
+            if (lastParentSurfaceHeight > 0) {
+                mParentScale.y = lastParentSurfaceHeight
+                        / (float) parentSurfaceOwnerView.getHeight();
+            } else {
+                mParentScale.y = 1.0f;
+            }
+            if (!contentPositionOrScaleChanged
+                    && Float.compare(oldParentScaleY, mParentScale.y) != 0) {
+                contentPositionOrScaleChanged = true;
+            }
+        } else if (mParentPosition != null || mParentScale != null) {
+            contentPositionOrScaleChanged = true;
+            mParentPosition = null;
+            mParentScale = null;
+        }
+
+        if (contentPositionOrScaleChanged) {
+            mSurfaceView.requestUpdateSurfacePositionAndScale();
+        }
+    }
 }
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 47edc49..a30c3c5 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -1017,14 +1017,17 @@
 
     /**
      * Update UI to reflect changes in data.
-     * <p>If {@code listAdapter} is {@code null}, both profile list adapters are updated.
+     * <p>If {@code listAdapter} is {@code null}, both profile list adapters are updated if
+     * available.
      */
     private void handlePackagesChanged(@Nullable ResolverListAdapter listAdapter) {
         // Refresh pinned items
         mPinnedSharedPrefs = getPinnedSharedPrefs(this);
         if (listAdapter == null) {
             mChooserMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
-            mChooserMultiProfilePagerAdapter.getInactiveListAdapter().handlePackagesChanged();
+            if (mChooserMultiProfilePagerAdapter.getCount() > 1) {
+                mChooserMultiProfilePagerAdapter.getInactiveListAdapter().handlePackagesChanged();
+            }
         } else {
             listAdapter.handlePackagesChanged();
         }
diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
index 79397b8..4b968b4 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -46,7 +46,8 @@
         SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE,
         SoftInputShowHideReason.HIDE_POWER_BUTTON_GO_HOME,
         SoftInputShowHideReason.HIDE_DOCKED_STACK_ATTACHED,
-        SoftInputShowHideReason.HIDE_RECENTS_ANIMATION})
+        SoftInputShowHideReason.HIDE_RECENTS_ANIMATION,
+        SoftInputShowHideReason.HIDE_BUBBLES})
 public @interface SoftInputShowHideReason {
     /** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */
     int SHOW_SOFT_INPUT = 0;
@@ -140,4 +141,10 @@
      * intercept touch from app window.
      */
     int HIDE_RECENTS_ANIMATION = 18;
+
+    /**
+     * Hide soft input when {@link com.android.systemui.bubbles.BubbleController} is expanding,
+     * switching, or collapsing Bubbles.
+     */
+    int HIDE_BUBBLES = 19;
 }
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index c320824..4999ec0 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -79,6 +79,7 @@
     void onNotificationSettingsViewed(String key);
     void onNotificationBubbleChanged(String key, boolean isBubble, int flags);
     void onBubbleNotificationSuppressionChanged(String key, boolean isSuppressed);
+    void hideCurrentInputMethodForBubbles();
     void grantInlineReplyUriPermission(String key, in Uri uri, in UserHandle user, String packageName);
     void clearInlineReplyUriPermissions(String key);
 
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 3fcedd9..0791ed3 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -1304,8 +1304,10 @@
         if (expandable) {
             mExpandButtonContainer.setVisibility(VISIBLE);
             mExpandButtonInnerContainer.setOnClickListener(onClickListener);
+            mConversationIconContainer.setOnClickListener(onClickListener);
         } else {
             mExpandButtonContainer.setVisibility(GONE);
+            mConversationIconContainer.setOnClickListener(null);
         }
         updateContentEndPaddings();
     }
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index bf7f339..1b32725 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -26,13 +26,11 @@
 import static junit.framework.TestCase.assertFalse;
 import static junit.framework.TestCase.assertTrue;
 
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
 
 import android.app.Instrumentation;
 import android.content.Context;
@@ -135,37 +133,29 @@
         InsetsSourceConsumer consumer = new InsetsSourceConsumer(
                 ITYPE_IME, state, null, controller);
 
-        when(controller.getAnimationType(anyInt())).thenReturn(ANIMATION_TYPE_NONE);
-
         InsetsSource source = new InsetsSource(ITYPE_IME);
         source.setFrame(0, 1, 2, 3);
-        consumer.updateSource(new InsetsSource(source));
-
-        when(controller.getAnimationType(anyInt())).thenReturn(ANIMATION_TYPE_USER);
+        consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_NONE);
 
         // While we're animating, updates are delayed
         source.setFrame(4, 5, 6, 7);
-        consumer.updateSource(new InsetsSource(source));
+        consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_USER);
         assertEquals(new Rect(0, 1, 2, 3), state.peekSource(ITYPE_IME).getFrame());
 
         // Finish the animation, now the pending frame should be applied
-        when(controller.getAnimationType(anyInt())).thenReturn(ANIMATION_TYPE_NONE);
         assertTrue(consumer.notifyAnimationFinished());
         assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ITYPE_IME).getFrame());
 
-        when(controller.getAnimationType(anyInt())).thenReturn(ANIMATION_TYPE_USER);
-
         // Animating again, updates are delayed
         source.setFrame(8, 9, 10, 11);
-        consumer.updateSource(new InsetsSource(source));
+        consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_USER);
         assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ITYPE_IME).getFrame());
 
         // Updating with the current frame triggers a different code path, verify this clears
         // the pending 8, 9, 10, 11 frame:
         source.setFrame(4, 5, 6, 7);
-        consumer.updateSource(new InsetsSource(source));
+        consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_USER);
 
-        when(controller.getAnimationType(anyInt())).thenReturn(ANIMATION_TYPE_NONE);
         assertFalse(consumer.notifyAnimationFinished());
         assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ITYPE_IME).getFrame());
     }
diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java
index 493c85a..4eb6e42 100644
--- a/identity/java/android/security/identity/IdentityCredential.java
+++ b/identity/java/android/security/identity/IdentityCredential.java
@@ -41,19 +41,18 @@
     /**
      * Create an ephemeral key pair to use to establish a secure channel with a reader.
      *
-     * <p>Most applications will use only the public key, and only to send it to the reader,
-     * allowing the private key to be used internally for {@link #encryptMessageToReader(byte[])}
-     * and {@link #decryptMessageFromReader(byte[])}. The private key is also provided for
-     * applications that wish to use a cipher suite that is not supported by
-     * {@link IdentityCredentialStore}.
+     * <p>Applications should use this key-pair for the communications channel with the reader
+     * using a protocol / cipher-suite appropriate for the application. One example of such a
+     * protocol is the one used for Mobile Driving Licenses, see ISO 18013-5 section 9.2.1 "Session
+     * encryption".
      *
      * @return ephemeral key pair to use to establish a secure channel with a reader.
      */
     public @NonNull abstract KeyPair createEphemeralKeyPair();
 
     /**
-     * Set the ephemeral public key provided by the reader. This must be called before
-     * {@link #encryptMessageToReader} or {@link #decryptMessageFromReader} can be called.
+     * Set the ephemeral public key provided by the reader. If called, this must be called before
+     * {@link #getEntries(byte[], Map, byte[], byte[])} is called.
      *
      * @param readerEphemeralPublicKey The ephemeral public key provided by the reader to
      *                                 establish a secure session.
@@ -65,6 +64,11 @@
     /**
      * Encrypt a message for transmission to the reader.
      *
+     * <p>Do not use. In this version of the API, this method produces an incorrect
+     * result. Instead, applications should implement message encryption/decryption themselves as
+     * detailed in the {@link #createEphemeralKeyPair()} method. In a future API-level, this
+     * method will be deprecated.
+     *
      * @param messagePlaintext unencrypted message to encrypt.
      * @return encrypted message.
      */
@@ -73,6 +77,11 @@
     /**
      * Decrypt a message received from the reader.
      *
+     * <p>Do not use. In this version of the API, this method produces an incorrect
+     * result. Instead, applications should implement message encryption/decryption themselves as
+     * detailed in the {@link #createEphemeralKeyPair()} method. In a future API-level, this
+     * method will be deprecated.
+     *
      * @param messageCiphertext encrypted message to decrypt.
      * @return decrypted message.
      * @throws MessageDecryptionException if the ciphertext couldn't be decrypted.
@@ -178,7 +187,7 @@
      *
      * <p>If {@code readerAuth} is not {@code null} it must be the bytes of a {@code COSE_Sign1}
      * structure as defined in RFC 8152. For the payload nil shall be used and the
-     * detached payload is the ReaderAuthentication CBOR described below.
+     * detached payload is the ReaderAuthenticationBytes CBOR described below.
      * <pre>
      *     ReaderAuthentication = [
      *       "ReaderAuthentication",
@@ -186,7 +195,9 @@
      *       ItemsRequestBytes
      *     ]
      *
-     *     ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest)   ; Bytes of ItemsRequest
+     *     ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest)
+     *
+     *     ReaderAuthenticationBytes = #6.24(bstr .cbor ReaderAuthentication)
      * </pre>
      *
      * <p>where {@code ItemsRequestBytes} are the bytes in the {@code requestMessage} parameter.
diff --git a/identity/java/android/security/identity/ResultData.java b/identity/java/android/security/identity/ResultData.java
index 37de2c4..71860d2 100644
--- a/identity/java/android/security/identity/ResultData.java
+++ b/identity/java/android/security/identity/ResultData.java
@@ -68,8 +68,8 @@
      * {@link #getMessageAuthenticationCode()} can be used to get a MAC.
      *
      * <p>The CBOR structure which is cryptographically authenticated is the
-     * {@code DeviceAuthentication} structure according to the following
-     * <a href="https://tools.ietf.org/html/draft-ietf-cbor-cddl-06">CDDL</a> schema:
+     * {@code DeviceAuthenticationBytes} structure according to the following
+     * <a href="https://tools.ietf.org/html/rfc8610">CDDL</a> schema:
      *
      * <pre>
      *   DeviceAuthentication = [
@@ -80,15 +80,9 @@
      *   ]
      *
      *   DocType = tstr
-     *
-     *   SessionTranscript = [
-     *     DeviceEngagementBytes,
-     *     EReaderKeyBytes
-     *   ]
-     *
-     *   DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement)
-     *   EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)
+     *   SessionTranscript = any
      *   DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces)
+     *   DeviceAuthenticationBytes = #6.24(bstr .cbor DeviceAuthentication)
      * </pre>
      *
      * <p>where
@@ -115,7 +109,7 @@
     public abstract @NonNull byte[] getAuthenticatedData();
 
     /**
-     * Returns a message authentication code over the {@code DeviceAuthentication} CBOR
+     * Returns a message authentication code over the {@code DeviceAuthenticationBytes} CBOR
      * specified in {@link #getAuthenticatedData()}, to prove to the reader that the data
      * is from a trusted credential.
      *
diff --git a/location/java/android/location/AbstractListenerManager.java b/location/java/android/location/AbstractListenerManager.java
index 36b8689..f5595e8 100644
--- a/location/java/android/location/AbstractListenerManager.java
+++ b/location/java/android/location/AbstractListenerManager.java
@@ -29,6 +29,8 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -193,7 +195,7 @@
     protected abstract void unregisterService() throws RemoteException;
 
     @Nullable
-    protected TRequest merge(@NonNull TRequest[] requests) {
+    protected TRequest merge(@NonNull List<TRequest> requests) {
         for (TRequest request : requests) {
             Preconditions.checkArgument(request == null,
                     "merge() has to be overridden for non-null requests.");
@@ -221,9 +223,9 @@
             return mListeners.valueAt(0).getRequest();
         }
 
-        TRequest[] requests = (TRequest[]) new Object[mListeners.size()];
+        ArrayList<TRequest> requests = new ArrayList<>(mListeners.size());
         for (int index = 0; index < mListeners.size(); index++) {
-            requests[index] = mListeners.valueAt(index).getRequest();
+            requests.add(mListeners.valueAt(index).getRequest());
         }
         return merge(requests);
     }
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 7d15bbd..f3c9e94 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -3030,14 +3030,14 @@
 
         @Override
         @Nullable
-        protected GnssRequest merge(@NonNull GnssRequest[] requests) {
-            Preconditions.checkArgument(requests.length > 0);
+        protected GnssRequest merge(@NonNull List<GnssRequest> requests) {
+            Preconditions.checkArgument(!requests.isEmpty());
             for (GnssRequest request : requests) {
                 if (request.isFullTracking()) {
                     return request;
                 }
             }
-            return requests[0];
+            return requests.get(0);
         }
 
         private class GnssMeasurementsListener extends IGnssMeasurementsListener.Stub {
diff --git a/media/java/android/media/IMediaRouter2.aidl b/media/java/android/media/IMediaRouter2.aidl
index ca14052..fe15f0e 100644
--- a/media/java/android/media/IMediaRouter2.aidl
+++ b/media/java/android/media/IMediaRouter2.aidl
@@ -34,7 +34,8 @@
     void notifySessionReleased(in RoutingSessionInfo sessionInfo);
     /**
      * Gets hints of the new session for the given route.
-     * Call MediaRouterService#notifySessionHintsForCreatingSession to pass the result.
+     * Call MediaRouterService#requestCreateSessionWithRouter2 to pass the result.
      */
-    void getSessionHintsForCreatingSession(long uniqueRequestId, in MediaRoute2Info route);
+    void requestCreateSessionByManager(long uniqueRequestId, in RoutingSessionInfo oldSession,
+        in MediaRoute2Info route);
 }
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 52bac67..068f968 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -44,7 +44,7 @@
     void requestSetVolume(IMediaRouterClient client, String routeId, int volume);
     void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction);
 
-    // Note: When changing this file, match the order of methods below with 
+    // Note: When changing this file, match the order of methods below with
     // MediaRouterService.java for readability.
 
     // Methods for MediaRouter2
@@ -57,10 +57,9 @@
             in RouteDiscoveryPreference preference);
     void setRouteVolumeWithRouter2(IMediaRouter2 router, in MediaRoute2Info route, int volume);
 
-    void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId,
-            in MediaRoute2Info route, in @nullable Bundle sessionHints);
-    void notifySessionHintsForCreatingSession(IMediaRouter2 router, long uniqueRequestId,
-                in MediaRoute2Info route, in @nullable Bundle sessionHints);
+    void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId, long managerRequestId,
+            in RoutingSessionInfo oldSession, in MediaRoute2Info route,
+            in @nullable Bundle sessionHints);
     void selectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route);
     void deselectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route);
     void transferToRouteWithRouter2(IMediaRouter2 router, String sessionId,
@@ -76,7 +75,7 @@
             in MediaRoute2Info route, int volume);
 
     void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId,
-            String packageName, in @nullable MediaRoute2Info route);
+            in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route);
     void selectRouteWithManager(IMediaRouter2Manager manager, int requestId,
             String sessionId, in MediaRoute2Info route);
     void deselectRouteWithManager(IMediaRouter2Manager manager, int requestId,
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 62d76c0..0780c68 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -2762,7 +2762,7 @@
                 builder.append(hexdigits.charAt(key[i] & 0x0f));
             }
             builder.append("], iv [");
-            for (int i = 0; i < key.length; i++) {
+            for (int i = 0; i < iv.length; i++) {
                 builder.append(hexdigits.charAt((iv[i] & 0xf0) >> 4));
                 builder.append(hexdigits.charAt(iv[i] & 0x0f));
             }
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 8e95239..f22222d 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -36,7 +36,6 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -62,6 +61,11 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private static final Object sRouterLock = new Object();
 
+    // The maximum time for the old routing controller available after transfer.
+    private static final int TRANSFER_TIMEOUT_MS = 30_000;
+    // The manager request ID representing that no manager is involved.
+    private static final long MANAGER_REQUEST_ID_NONE = MediaRoute2ProviderService.REQUEST_ID_NONE;
+
     @GuardedBy("sRouterLock")
     private static MediaRouter2 sInstance;
 
@@ -80,7 +84,7 @@
 
     private final String mPackageName;
     @GuardedBy("sRouterLock")
-    final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
+    final Map<String, MediaRoute2Info> mRoutes = new ArrayMap<>();
 
     final RoutingController mSystemController;
 
@@ -94,7 +98,7 @@
     @GuardedBy("sRouterLock")
     private final Map<String, RoutingController> mNonSystemRoutingControllers = new ArrayMap<>();
 
-    private final AtomicInteger mControllerCreationRequestCnt = new AtomicInteger(1);
+    private final AtomicInteger mNextRequestId = new AtomicInteger(1);
 
     final Handler mHandler;
     @GuardedBy("sRouterLock")
@@ -412,9 +416,16 @@
             return;
         }
 
-        final int requestId = mControllerCreationRequestCnt.getAndIncrement();
+        requestCreateController(controller, route, MANAGER_REQUEST_ID_NONE);
+    }
 
-        ControllerCreationRequest request = new ControllerCreationRequest(requestId, route);
+    void requestCreateController(@NonNull RoutingController controller,
+            @NonNull MediaRoute2Info route, long managerRequestId) {
+
+        final int requestId = mNextRequestId.getAndIncrement();
+
+        ControllerCreationRequest request = new ControllerCreationRequest(requestId,
+                managerRequestId, route, controller);
         mControllerCreationRequests.add(request);
 
         OnGetControllerHintsListener listener = mOnGetControllerHintsListener;
@@ -433,11 +444,15 @@
         if (stub != null) {
             try {
                 mMediaRouterService.requestCreateSessionWithRouter2(
-                        stub, requestId, route, controllerHints);
+                        stub, requestId, managerRequestId,
+                        controller.getRoutingSessionInfo(), route, controllerHints);
             } catch (RemoteException ex) {
-                Log.e(TAG, "transfer: Unable to request to create controller.", ex);
-                mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler,
-                        MediaRouter2.this, requestId, null));
+                Log.e(TAG, "createControllerForTransfer: "
+                        + "Failed to request for creating a controller.", ex);
+                mControllerCreationRequests.remove(request);
+                if (managerRequestId == MANAGER_REQUEST_ID_NONE) {
+                    notifyTransferFailure(route);
+                }
             }
         }
     }
@@ -463,7 +478,8 @@
     }
 
     /**
-     * Gets the list of currently non-released {@link RoutingController routing controllers}.
+     * Gets the list of currently active {@link RoutingController routing controllers} on which
+     * media can be played.
      * <p>
      * Note: The list returned here will never be empty. The first element in the list is
      * always the {@link #getSystemController() system controller}.
@@ -554,13 +570,13 @@
             mShouldUpdateRoutes = true;
         }
 
-        if (addedRoutes.size() > 0) {
+        if (!addedRoutes.isEmpty()) {
             notifyRoutesAdded(addedRoutes);
         }
-        if (removedRoutes.size() > 0) {
+        if (!removedRoutes.isEmpty()) {
             notifyRoutesRemoved(removedRoutes);
         }
-        if (changedRoutes.size() > 0) {
+        if (!changedRoutes.isEmpty()) {
             notifyRoutesChanged(changedRoutes);
         }
 
@@ -582,7 +598,7 @@
             }
             mShouldUpdateRoutes = true;
         }
-        if (addedRoutes.size() > 0) {
+        if (!addedRoutes.isEmpty()) {
             notifyRoutesAdded(addedRoutes);
         }
     }
@@ -598,7 +614,7 @@
             }
             mShouldUpdateRoutes = true;
         }
-        if (removedRoutes.size() > 0) {
+        if (!removedRoutes.isEmpty()) {
             notifyRoutesRemoved(removedRoutes);
         }
     }
@@ -614,7 +630,7 @@
             }
             mShouldUpdateRoutes = true;
         }
-        if (changedRoutes.size() > 0) {
+        if (!changedRoutes.isEmpty()) {
             notifyRoutesChanged(changedRoutes);
         }
     }
@@ -635,44 +651,47 @@
             }
         }
 
-        if (matchingRequest != null) {
-            mControllerCreationRequests.remove(matchingRequest);
-
-            MediaRoute2Info requestedRoute = matchingRequest.mRoute;
-
-            if (sessionInfo == null) {
-                // TODO: We may need to distinguish between failure and rejection.
-                //       One way can be introducing 'reason'.
-                notifyTransferFailure(requestedRoute);
-                return;
-            } else if (!sessionInfo.getSelectedRoutes().contains(requestedRoute.getId())) {
-                Log.w(TAG, "The session does not contain the requested route. "
-                        + "(requestedRouteId=" + requestedRoute.getId()
-                        + ", actualRoutes=" + sessionInfo.getSelectedRoutes()
-                        + ")");
-                notifyTransferFailure(requestedRoute);
-                return;
-            } else if (!TextUtils.equals(requestedRoute.getProviderId(),
-                    sessionInfo.getProviderId())) {
-                Log.w(TAG, "The session's provider ID does not match the requested route's. "
-                        + "(requested route's providerId=" + requestedRoute.getProviderId()
-                        + ", actual providerId=" + sessionInfo.getProviderId()
-                        + ")");
-                notifyTransferFailure(requestedRoute);
-                return;
-            }
-        }
-
-        if (sessionInfo == null) {
+        if (matchingRequest == null) {
+            Log.w(TAG, "createControllerOnHandler: Ignoring an unknown request.");
             return;
         }
 
-        RoutingController oldController = getCurrentController();
-        if (!oldController.releaseInternal(
-                /* shouldReleaseSession= */ matchingRequest != null,
-                /* shouldNotifyStop= */ false)) {
-            // Could not release the controller since it was just released by other thread.
-            oldController = getSystemController();
+        mControllerCreationRequests.remove(matchingRequest);
+        MediaRoute2Info requestedRoute = matchingRequest.mRoute;
+
+        // TODO: Notify the reason for failure.
+        if (sessionInfo == null) {
+            notifyTransferFailure(requestedRoute);
+            return;
+        } else if (!sessionInfo.getSelectedRoutes().contains(requestedRoute.getId())) {
+            Log.w(TAG, "The session does not contain the requested route. "
+                    + "(requestedRouteId=" + requestedRoute.getId()
+                    + ", actualRoutes=" + sessionInfo.getSelectedRoutes()
+                    + ")");
+            notifyTransferFailure(requestedRoute);
+            return;
+        } else if (!TextUtils.equals(requestedRoute.getProviderId(),
+                sessionInfo.getProviderId())) {
+            Log.w(TAG, "The session's provider ID does not match the requested route's. "
+                    + "(requested route's providerId=" + requestedRoute.getProviderId()
+                    + ", actual providerId=" + sessionInfo.getProviderId()
+                    + ")");
+            notifyTransferFailure(requestedRoute);
+            return;
+        }
+
+        RoutingController oldController = matchingRequest.mOldController;
+        // When the old controller is released before transferred, treat it as a failure.
+        // This could also happen when transfer is requested twice or more.
+        if (!oldController.scheduleRelease()) {
+            Log.w(TAG, "createControllerOnHandler: "
+                    + "Ignoring controller creation for released old controller. "
+                    + "oldController=" + oldController);
+            if (!sessionInfo.isSystemSession()) {
+                new RoutingController(sessionInfo).release();
+            }
+            notifyTransferFailure(requestedRoute);
+            return;
         }
 
         RoutingController newController;
@@ -686,12 +705,7 @@
             }
         }
 
-        // Two controller can be same if stop() is called before the result of Cast -> Phone comes.
-        if (oldController != newController) {
-            notifyTransfer(oldController, newController);
-        } else if (matchingRequest != null) {
-            notifyTransferFailure(matchingRequest.mRoute);
-        }
+        notifyTransfer(oldController, newController);
     }
 
     void updateControllerOnHandler(RoutingSessionInfo sessionInfo) {
@@ -736,10 +750,9 @@
             return;
         }
 
-        final String uniqueSessionId = sessionInfo.getId();
         RoutingController matchingController;
         synchronized (sRouterLock) {
-            matchingController = mNonSystemRoutingControllers.get(uniqueSessionId);
+            matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId());
         }
 
         if (matchingController == null) {
@@ -757,34 +770,23 @@
             return;
         }
 
-        matchingController.releaseInternal(
-                /* shouldReleaseSession= */ false, /* shouldNotifyStop= */ true);
+        matchingController.releaseInternal(/* shouldReleaseSession= */ false);
     }
 
-    void onGetControllerHintsForCreatingSessionOnHandler(long uniqueRequestId,
-            MediaRoute2Info route) {
-        OnGetControllerHintsListener listener = mOnGetControllerHintsListener;
-        Bundle controllerHints = null;
-        if (listener != null) {
-            controllerHints = listener.onGetControllerHints(route);
-            if (controllerHints != null) {
-                controllerHints = new Bundle(controllerHints);
+    void onRequestCreateControllerByManagerOnHandler(RoutingSessionInfo oldSession,
+            MediaRoute2Info route, long managerRequestId) {
+        RoutingController controller;
+        if (oldSession.isSystemSession()) {
+            controller = getSystemController();
+        } else {
+            synchronized (sRouterLock) {
+                controller = mNonSystemRoutingControllers.get(oldSession.getId());
             }
         }
-
-        MediaRouter2Stub stub;
-        synchronized (sRouterLock) {
-            stub = mStub;
+        if (controller == null) {
+            return;
         }
-        if (stub != null) {
-            try {
-                mMediaRouterService.notifySessionHintsForCreatingSession(
-                        stub, uniqueRequestId, route, controllerHints);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "onGetControllerHintsForCreatingSessionOnHandler: Unable to notify "
-                        + " session hints for creating session.", ex);
-            }
-        }
+        requestCreateController(controller, route, managerRequestId);
     }
 
     private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes,
@@ -886,8 +888,13 @@
         /**
          * Called when a media is transferred between two different routing controllers.
          * This can happen by calling {@link #transferTo(MediaRoute2Info)}.
-         * The {@code oldController} is released before this method is called, except for the
-         * {@link #getSystemController() system controller}.
+         * <p> Override this to start playback with {@code newController}. You may want to get
+         * the status of the media that is being played with {@code oldController} and resume it
+         * continuously with {@code newController}.
+         * After this is called, any callbacks with {@code oldController} will not be invoked
+         * unless {@code oldController} is the {@link #getSystemController() system controller}.
+         * You need to {@link RoutingController#release() release} {@code oldController} before
+         * playing the media with {@code newController}.
          *
          * @param oldController the previous controller that controlled routing
          * @param newController the new controller to control routing
@@ -906,16 +913,15 @@
         /**
          * Called when a media routing stops. It can be stopped by a user or a provider.
          * App should not continue playing media locally when this method is called.
-         * The {@code oldController} is released before this method is called, except for the
-         * {@link #getSystemController() system controller}.
+         * The {@code controller} is released before this method is called.
          *
-         * @param controller the controller that controlled the stopped media routing.
+         * @param controller the controller that controlled the stopped media routing
          */
         public void onStop(@NonNull RoutingController controller) { }
     }
 
     /**
-     * A listener interface to send an optional app-specific hints when creating the
+     * A listener interface to send optional app-specific hints when creating a
      * {@link RoutingController}.
      */
     public interface OnGetControllerHintsListener {
@@ -929,9 +935,9 @@
          * The method will be called on the same thread that calls
          * {@link #transferTo(MediaRoute2Info)} or the main thread if it is requested by the system.
          *
-         * @param route The route to create controller with
+         * @param route the route to create a controller with
          * @return An optional bundle of app-specific arguments to send to the provider,
-         *         or null if none. The contents of this bundle may affect the result of
+         *         or {@code null} if none. The contents of this bundle may affect the result of
          *         controller creation.
          * @see MediaRoute2ProviderService#onCreateSession(long, String, String, Bundle)
          */
@@ -944,10 +950,11 @@
      */
     public abstract static class ControllerCallback {
         /**
-         * Called when a controller is updated. (e.g., the selected routes of the
-         * controller is changed or the volume of the controller is changed.)
+         * Called when a controller is updated. (e.g., when the selected routes of the
+         * controller is changed or when the volume of the controller is changed.)
          *
-         * @param controller the updated controller. Can be the system controller.
+         * @param controller the updated controller. It may be the
+         * {@link #getSystemController() system controller}.
          * @see #getSystemController()
          */
         public void onControllerUpdated(@NonNull RoutingController controller) { }
@@ -955,20 +962,28 @@
 
     /**
      * A class to control media routing session in media route provider.
-     * For example, selecting/deselecting/transferring routes to a session can be done through this
-     * class. Instances are created by {@link #transferTo(MediaRoute2Info)}.
+     * For example, selecting/deselecting/transferring to routes of a session can be done through
+     * this. Instances are created when
+     * {@link TransferCallback#onTransfer(RoutingController, RoutingController)} is called,
+     * which is invoked after {@link #transferTo(MediaRoute2Info)} is called.
      */
     public class RoutingController {
         private final Object mControllerLock = new Object();
 
+        private static final int CONTROLLER_STATE_UNKNOWN = 0;
+        private static final int CONTROLLER_STATE_ACTIVE = 1;
+        private static final int CONTROLLER_STATE_RELEASING = 2;
+        private static final int CONTROLLER_STATE_RELEASED = 3;
+
         @GuardedBy("mControllerLock")
         private RoutingSessionInfo mSessionInfo;
 
         @GuardedBy("mControllerLock")
-        private volatile boolean mIsReleased;
+        private int mState;
 
         RoutingController(@NonNull RoutingSessionInfo sessionInfo) {
             mSessionInfo = sessionInfo;
+            mState = CONTROLLER_STATE_ACTIVE;
         }
 
         /**
@@ -982,7 +997,7 @@
         }
 
         /**
-         * Gets the original session id set by
+         * Gets the original session ID set by
          * {@link RoutingSessionInfo.Builder#Builder(String, String)}.
          *
          * @hide
@@ -996,7 +1011,8 @@
         }
 
         /**
-         * @return the control hints used to control routing session if available.
+         * Gets the control hints used to control routing session if available.
+         * It is set by the media route provider.
          */
         @Nullable
         public Bundle getControlHints() {
@@ -1042,7 +1058,9 @@
         }
 
         /**
-         * Gets information about how volume is handled on the session.
+         * Gets the information about how volume is handled on the session.
+         * <p>Please note that you may not control the volume of the session even when
+         * you can control the volume of each selected route in the session.
          *
          * @return {@link MediaRoute2Info#PLAYBACK_VOLUME_FIXED} or
          * {@link MediaRoute2Info#PLAYBACK_VOLUME_VARIABLE}
@@ -1067,8 +1085,8 @@
          * Gets the current volume of the session.
          * <p>
          * When it's available, it represents the volume of routing session, which is a group
-         * of selected routes. To get the volume of a route,
-         * use {@link MediaRoute2Info#getVolume()}.
+         * of selected routes. Use {@link MediaRoute2Info#getVolume()}
+         * to get the volume of a route,
          * </p>
          * @see MediaRoute2Info#getVolume()
          */
@@ -1087,7 +1105,7 @@
          */
         public boolean isReleased() {
             synchronized (mControllerLock) {
-                return mIsReleased;
+                return mState == CONTROLLER_STATE_RELEASED;
             }
         }
 
@@ -1099,8 +1117,8 @@
          * <p>
          * The given route must satisfy all of the following conditions:
          * <ul>
-         * <li>ID should not be included in {@link #getSelectedRoutes()}</li>
-         * <li>ID should be included in {@link #getSelectableRoutes()}</li>
+         * <li>It should not be included in {@link #getSelectedRoutes()}</li>
+         * <li>It should be included in {@link #getSelectableRoutes()}</li>
          * </ul>
          * If the route doesn't meet any of above conditions, it will be ignored.
          *
@@ -1111,11 +1129,9 @@
          */
         public void selectRoute(@NonNull MediaRoute2Info route) {
             Objects.requireNonNull(route, "route must not be null");
-            synchronized (mControllerLock) {
-                if (mIsReleased) {
-                    Log.w(TAG, "selectRoute: Called on released controller. Ignoring.");
-                    return;
-                }
+            if (isReleased()) {
+                Log.w(TAG, "selectRoute: Called on released controller. Ignoring.");
+                return;
             }
 
             List<MediaRoute2Info> selectedRoutes = getSelectedRoutes();
@@ -1145,12 +1161,12 @@
 
         /**
          * Deselects a route from the remote session. After a route is deselected, the media is
-         * expected to be stopped on the deselected routes.
+         * expected to be stopped on the deselected route.
          * <p>
          * The given route must satisfy all of the following conditions:
          * <ul>
-         * <li>ID should be included in {@link #getSelectedRoutes()}</li>
-         * <li>ID should be included in {@link #getDeselectableRoutes()}</li>
+         * <li>It should be included in {@link #getSelectedRoutes()}</li>
+         * <li>It should be included in {@link #getDeselectableRoutes()}</li>
          * </ul>
          * If the route doesn't meet any of above conditions, it will be ignored.
          *
@@ -1160,11 +1176,9 @@
          */
         public void deselectRoute(@NonNull MediaRoute2Info route) {
             Objects.requireNonNull(route, "route must not be null");
-            synchronized (mControllerLock) {
-                if (mIsReleased) {
-                    Log.w(TAG, "deselectRoute: called on released controller. Ignoring.");
-                    return;
-                }
+            if (isReleased()) {
+                Log.w(TAG, "deselectRoute: called on released controller. Ignoring.");
+                return;
             }
 
             List<MediaRoute2Info> selectedRoutes = getSelectedRoutes();
@@ -1193,13 +1207,8 @@
         }
 
         /**
-         * Transfers to a given route for the remote session. The given route must satisfy
-         * all of the following conditions:
-         * <ul>
-         * <li>ID should not be included in {@link RoutingSessionInfo#getSelectedRoutes()}</li>
-         * <li>ID should be included in {@link RoutingSessionInfo#getTransferableRoutes()}</li>
-         * </ul>
-         * If the route doesn't meet any of above conditions, it will be ignored.
+         * Transfers to a given route for the remote session. The given route must be included
+         * in {@link RoutingSessionInfo#getTransferableRoutes()}.
          *
          * @see RoutingSessionInfo#getSelectedRoutes()
          * @see RoutingSessionInfo#getTransferableRoutes()
@@ -1208,19 +1217,13 @@
         void transferToRoute(@NonNull MediaRoute2Info route) {
             Objects.requireNonNull(route, "route must not be null");
             synchronized (mControllerLock) {
-                if (mIsReleased) {
+                if (isReleased()) {
                     Log.w(TAG, "transferToRoute: Called on released controller. Ignoring.");
                     return;
                 }
 
-                if (mSessionInfo.getSelectedRoutes().contains(route.getId())) {
-                    Log.w(TAG, "Ignoring transferring to a route that is already added. "
-                            + "route=" + route);
-                    return;
-                }
-
                 if (!mSessionInfo.getTransferableRoutes().contains(route.getId())) {
-                    Log.w(TAG, "Ignoring transferring to a non-transferrable route=" + route);
+                    Log.w(TAG, "Ignoring transferring to a non-transferable route=" + route);
                     return;
                 }
             }
@@ -1255,11 +1258,9 @@
                 return;
             }
 
-            synchronized (mControllerLock) {
-                if (mIsReleased) {
-                    Log.w(TAG, "setVolume: Called on released controller. Ignoring.");
-                    return;
-                }
+            if (isReleased()) {
+                Log.w(TAG, "setVolume: Called on released controller. Ignoring.");
+                return;
             }
             MediaRouter2Stub stub;
             synchronized (sRouterLock) {
@@ -1275,33 +1276,58 @@
         }
 
         /**
-         * Release this controller and corresponding session.
+         * Releases this controller and the corresponding session.
          * Any operations on this controller after calling this method will be ignored.
          * The devices that are playing media will stop playing it.
          */
-        // TODO(b/157872573): Add tests using {@link MediaRouter2Manager#getActiveSessions()}.
         public void release() {
-            releaseInternal(/* shouldReleaseSession= */ true, /* shouldNotifyStop= */ true);
+            releaseInternal(/* shouldReleaseSession= */ true);
         }
 
         /**
-         * Returns {@code true} when succeeded to release, {@code false} if the controller is
-         * already released.
+         * Schedules release of the controller.
+         * @return {@code true} if it's successfully scheduled, {@code false} if it's already
+         * scheduled to be released or released.
          */
-        boolean releaseInternal(boolean shouldReleaseSession, boolean shouldNotifyStop) {
+        boolean scheduleRelease() {
             synchronized (mControllerLock) {
-                if (mIsReleased) {
-                    Log.w(TAG, "releaseInternal: Called on released controller. Ignoring.");
+                if (mState != CONTROLLER_STATE_ACTIVE) {
                     return false;
                 }
-                mIsReleased = true;
+                mState = CONTROLLER_STATE_RELEASING;
             }
 
             synchronized (sRouterLock) {
+                // It could happen if the controller is released by the another thread
+                // in between two locks
                 if (!mNonSystemRoutingControllers.remove(getId(), this)) {
-                    Log.w(TAG, "releaseInternal: Ignoring unknown controller.");
-                    return false;
+                    // In that case, onStop isn't called so we return true to call onTransfer.
+                    // It's also consistent with that the another thread acquires the lock later.
+                    return true;
                 }
+            }
+
+            mHandler.postDelayed(this::release, TRANSFER_TIMEOUT_MS);
+
+            return true;
+        }
+
+        void releaseInternal(boolean shouldReleaseSession) {
+            boolean shouldNotifyStop;
+
+            synchronized (mControllerLock) {
+                if (mState == CONTROLLER_STATE_RELEASED) {
+                    if (DEBUG) {
+                        Log.d(TAG, "releaseInternal: Called on released controller. Ignoring.");
+                    }
+                    return;
+                }
+                shouldNotifyStop = (mState == CONTROLLER_STATE_ACTIVE);
+                mState = CONTROLLER_STATE_RELEASED;
+            }
+
+            synchronized (sRouterLock) {
+                mNonSystemRoutingControllers.remove(getId(), this);
 
                 if (shouldReleaseSession && mStub != null) {
                     try {
@@ -1326,7 +1352,6 @@
                     mStub = null;
                 }
             }
-            return true;
         }
 
         @Override
@@ -1389,9 +1414,14 @@
         }
 
         @Override
-        boolean releaseInternal(boolean shouldReleaseSession, boolean shouldNotifyStop) {
+        boolean scheduleRelease() {
+            // SystemRoutingController can be always transferred
+            return true;
+        }
+
+        @Override
+        void releaseInternal(boolean shouldReleaseSession) {
             // Do nothing. SystemRoutingController will never be released
-            return false;
         }
     }
 
@@ -1442,8 +1472,7 @@
             if (!(obj instanceof TransferCallbackRecord)) {
                 return false;
             }
-            return mTransferCallback
-                    == ((TransferCallbackRecord) obj).mTransferCallback;
+            return mTransferCallback == ((TransferCallbackRecord) obj).mTransferCallback;
         }
 
         @Override
@@ -1481,11 +1510,17 @@
 
     static final class ControllerCreationRequest {
         public final int mRequestId;
+        public final long mManagerRequestId;
         public final MediaRoute2Info mRoute;
+        public final RoutingController mOldController;
 
-        ControllerCreationRequest(int requestId, @NonNull MediaRoute2Info route) {
+        ControllerCreationRequest(int requestId, long managerRequestId,
+                @NonNull MediaRoute2Info route, @NonNull RoutingController oldController) {
             mRequestId = requestId;
-            mRoute = route;
+            mManagerRequestId = managerRequestId;
+            mRoute = Objects.requireNonNull(route, "route must not be null");
+            mOldController = Objects.requireNonNull(oldController,
+                    "oldController must not be null");
         }
     }
 
@@ -1534,11 +1569,11 @@
         }
 
         @Override
-        public void getSessionHintsForCreatingSession(long uniqueRequestId,
-                @NonNull MediaRoute2Info route) {
+        public void requestCreateSessionByManager(long managerRequestId,
+                RoutingSessionInfo oldSession, MediaRoute2Info route) {
             mHandler.sendMessage(obtainMessage(
-                    MediaRouter2::onGetControllerHintsForCreatingSessionOnHandler,
-                    MediaRouter2.this, uniqueRequestId, route));
+                    MediaRouter2::onRequestCreateControllerByManagerOnHandler,
+                    MediaRouter2.this, oldSession, route, managerRequestId));
         }
     }
 }
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index dad7859..4b09a5f 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -54,6 +54,12 @@
 public final class MediaRouter2Manager {
     private static final String TAG = "MR2Manager";
     private static final Object sLock = new Object();
+    /**
+     * The request ID for requests not asked by this instance.
+     * Shouldn't be used for a valid request.
+     * @hide
+     */
+    public static final int REQUEST_ID_NONE = 0;
     /** @hide */
     @VisibleForTesting
     public static final int TRANSFER_TIMEOUT_MS = 30_000;
@@ -480,7 +486,6 @@
             notifyTransferFailed(matchingRequest.mOldSessionInfo, requestedRoute);
             return;
         }
-        releaseSession(matchingRequest.mOldSessionInfo);
         notifyTransferred(matchingRequest.mOldSessionInfo, sessionInfo);
     }
 
@@ -777,7 +782,7 @@
         if (client != null) {
             try {
                 mMediaRouterService.requestCreateSessionWithManager(
-                        client, requestId, oldSession.getClientPackageName(), route);
+                        client, requestId, oldSession, route);
             } catch (RemoteException ex) {
                 Log.e(TAG, "requestCreateSession: Failed to send a request", ex);
             }
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index edf1fc5..a5d25e0 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -220,7 +220,7 @@
     }
 
     /**
-     * Gets information about how volume is handled on the session.
+     * Gets the information about how volume is handled on the session.
      *
      * @return {@link MediaRoute2Info#PLAYBACK_VOLUME_FIXED} or
      * {@link MediaRoute2Info#PLAYBACK_VOLUME_VARIABLE}.
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index e8f18a5..515d610 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -335,7 +335,7 @@
     if (mLinearBlockObj != NULL) {
         return mLinearBlockObj;
     }
-    mIonHandle = new C2HandleIon(mAvHandle->data[0], mDataLength);
+    mIonHandle = new C2HandleIon(dup(mAvHandle->data[0]), mDataLength);
     std::shared_ptr<C2LinearBlock> block = _C2BlockFactory::CreateLinearBlock(mIonHandle);
 
     JNIEnv *env = AndroidRuntime::getJNIEnv();
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 0979627..ddefe26 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -126,6 +126,7 @@
         StubMediaRoute2ProviderService instance = StubMediaRoute2ProviderService.getInstance();
         if (instance != null) {
             instance.setProxy(null);
+            instance.setSpy(null);
         }
     }
 
@@ -425,6 +426,79 @@
 
     @Test
     @LargeTest
+    public void testTransferTwice() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
+        addRouterCallback(new RouteCallback() { });
+
+        CountDownLatch successLatch1 = new CountDownLatch(1);
+        CountDownLatch successLatch2 = new CountDownLatch(1);
+        CountDownLatch failureLatch = new CountDownLatch(1);
+        CountDownLatch managerOnSessionReleasedLatch = new CountDownLatch(1);
+        CountDownLatch serviceOnReleaseSessionLatch = new CountDownLatch(1);
+        List<RoutingSessionInfo> sessions = new ArrayList<>();
+
+        StubMediaRoute2ProviderService instance = StubMediaRoute2ProviderService.getInstance();
+        assertNotNull(instance);
+        instance.setSpy(new StubMediaRoute2ProviderService.Spy() {
+            @Override
+            public void onReleaseSession(long requestId, String sessionId) {
+                serviceOnReleaseSessionLatch.countDown();
+            }
+        });
+
+        addManagerCallback(new MediaRouter2Manager.Callback() {
+            @Override
+            public void onTransferred(RoutingSessionInfo oldSession,
+                    RoutingSessionInfo newSession) {
+                sessions.add(newSession);
+                if (successLatch1.getCount() > 0) {
+                    successLatch1.countDown();
+                } else {
+                    successLatch2.countDown();
+                }
+            }
+
+            @Override
+            public void onTransferFailed(RoutingSessionInfo session, MediaRoute2Info route) {
+                failureLatch.countDown();
+            }
+
+            @Override
+            public void onSessionReleased(RoutingSessionInfo session) {
+                managerOnSessionReleasedLatch.countDown();
+            }
+        });
+
+        MediaRoute2Info route1 = routes.get(ROUTE_ID1);
+        MediaRoute2Info route2 = routes.get(ROUTE_ID2);
+        assertNotNull(route1);
+        assertNotNull(route2);
+
+        mManager.selectRoute(mPackageName, route1);
+        assertTrue(successLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        mManager.selectRoute(mPackageName, route2);
+        assertTrue(successLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        // onTransferFailed/onSessionReleased should not be called.
+        assertFalse(failureLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+        assertFalse(managerOnSessionReleasedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+
+        assertEquals(2, sessions.size());
+        List<String> activeSessionIds = mManager.getActiveSessions().stream()
+                .map(RoutingSessionInfo::getId)
+                .collect(Collectors.toList());
+        // The old session shouldn't appear on the active session list.
+        assertFalse(activeSessionIds.contains(sessions.get(0).getId()));
+        assertTrue(activeSessionIds.contains(sessions.get(1).getId()));
+
+        assertFalse(serviceOnReleaseSessionLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+        mManager.releaseSession(sessions.get(0));
+        assertTrue(serviceOnReleaseSessionLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertFalse(managerOnSessionReleasedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    @LargeTest
     public void testTransfer_ignored_fails() throws Exception {
         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
         addRouterCallback(new RouteCallback() {});
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
index 4551876..a51e371 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
@@ -79,6 +79,7 @@
     @GuardedBy("sLock")
     private static StubMediaRoute2ProviderService sInstance;
     private Proxy mProxy;
+    private Spy mSpy;
 
     private void initializeRoutes() {
         MediaRoute2Info route1 = new MediaRoute2Info.Builder(ROUTE_ID1, ROUTE_NAME1)
@@ -256,6 +257,11 @@
 
     @Override
     public void onReleaseSession(long requestId, String sessionId) {
+        Spy spy = mSpy;
+        if (spy != null) {
+            spy.onReleaseSession(requestId, sessionId);
+        }
+
         RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
         if (sessionInfo == null) {
             return;
@@ -375,7 +381,21 @@
         mProxy = proxy;
     }
 
+    public void setSpy(@Nullable Spy spy) {
+        mSpy = spy;
+    }
+
+    /**
+     * It overrides the original service
+     */
     public static class Proxy {
         public void onSetRouteVolume(String routeId, int volume, long requestId) {}
     }
+
+    /**
+     * It gets notified but doesn't prevent the original methods to be called.
+     */
+    public static class Spy {
+        public void onReleaseSession(long requestId, String sessionId) {}
+    }
 }
diff --git a/packages/SettingsLib/AdaptiveIcon/res/values-night/colors.xml b/packages/SettingsLib/AdaptiveIcon/res/values-night/colors.xml
new file mode 100644
index 0000000..f985c1e
--- /dev/null
+++ b/packages/SettingsLib/AdaptiveIcon/res/values-night/colors.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <color name="advanced_outline_color">#FFFFFFFF</color> <!-- icon outline color -->
+</resources>
diff --git a/packages/SystemUI/res/layout/controls_management.xml b/packages/SystemUI/res/layout/controls_management.xml
index 46f79de..ae7f44d 100644
--- a/packages/SystemUI/res/layout/controls_management.xml
+++ b/packages/SystemUI/res/layout/controls_management.xml
@@ -69,7 +69,7 @@
                 android:layout_width="wrap_content"
                 android:layout_height="match_parent"
                 android:gravity="center_vertical"
-                android:text="See other apps"
+                android:text="@string/controls_favorite_see_other_apps"
                 style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
                 app:layout_constraintTop_toTopOf="parent"
                 app:layout_constraintBottom_toBottomOf="parent"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 0e3fa1e..db45a60 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2744,6 +2744,9 @@
     <!-- Controls management favorites screen, See other apps with changes made [CHAR LIMIT=NONE] -->
     <string name="controls_favorite_toast_no_changes">Changes not saved</string>
 
+    <!-- Controls management favorites screen. See other apps button [CHAR LIMIT=30] -->
+    <string name="controls_favorite_see_other_apps">See other apps</string>
+
     <!-- Controls management controls screen error on load message [CHAR LIMIT=NONE] -->
     <string name="controls_favorite_load_error">Controls could not be loaded. Check the <xliff:g id="app" example="System UI">%s</xliff:g> app to make sure that the app settings haven\u2019t changed.</string>
     <!-- Controls management controls screen no controls found on load message [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index a8bbdf6..6dc8322 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -71,6 +71,7 @@
     // Items that are typically loaded later
     private String mAppName;
     private ShortcutInfo mShortcutInfo;
+    private String mMetadataShortcutId;
     private BadgedImageView mIconView;
     private BubbleExpandedView mExpandedView;
 
@@ -136,6 +137,7 @@
             final int desiredHeight, final int desiredHeightResId, @Nullable final String title) {
         Objects.requireNonNull(key);
         Objects.requireNonNull(shortcutInfo);
+        mMetadataShortcutId = shortcutInfo.getId();
         mShortcutInfo = shortcutInfo;
         mKey = key;
         mFlags = 0;
@@ -218,6 +220,14 @@
         return mTitle;
     }
 
+    String getMetadataShortcutId() {
+        return mMetadataShortcutId;
+    }
+
+    boolean hasMetadataShortcutId() {
+        return (mMetadataShortcutId != null && !mMetadataShortcutId.isEmpty());
+    }
+
     /**
      * Call when the views should be removed, ensure this is called to clean up ActivityView
      * content.
@@ -350,9 +360,9 @@
         mAppUid = entry.getSbn().getUid();
         mInstanceId = entry.getSbn().getInstanceId();
         mFlyoutMessage = BubbleViewInfoTask.extractFlyoutMessage(entry);
-        mShortcutInfo = (entry.getBubbleMetadata() != null
-                && entry.getBubbleMetadata().getShortcutId() != null
-                && entry.getRanking() != null) ? entry.getRanking().getShortcutInfo() : null;
+        mShortcutInfo = (entry.getRanking() != null ? entry.getRanking().getShortcutInfo() : null);
+        mMetadataShortcutId = (entry.getBubbleMetadata() != null
+                ? entry.getBubbleMetadata().getShortcutId() : null);
         if (entry.getRanking() != null) {
             mIsVisuallyInterruptive = entry.getRanking().visuallyInterruptive();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index aa41719..ccfbd8f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -483,12 +483,13 @@
     }
 
     /**
-     * Dispatches a back press into the expanded Bubble's ActivityView if its IME is visible,
-     * causing it to hide.
+     * Hides the current input method, wherever it may be focused, via InputMethodManagerInternal.
      */
-    public void hideImeFromExpandedBubble() {
-        if (mStackView != null) {
-            mStackView.hideImeFromExpandedBubble();
+    public void hideCurrentInputMethod() {
+        try {
+            mBarService.hideCurrentInputMethodForBubbles();
+        } catch (RemoteException e) {
+            e.printStackTrace();
         }
     }
 
@@ -693,8 +694,8 @@
         if (mStackView == null) {
             mStackView = new BubbleStackView(
                     mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator,
-                    mSysUiState, this::onAllBubblesAnimatedOut,
-                    this::onImeVisibilityChanged);
+                    mSysUiState, this::onAllBubblesAnimatedOut, this::onImeVisibilityChanged,
+                    this::hideCurrentInputMethod);
             mStackView.addView(mBubbleScrim);
             if (mExpandListener != null) {
                 mStackView.setExpandListener(mExpandListener);
@@ -1589,7 +1590,11 @@
         @Override
         public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
             if (mStackView != null && taskInfo.displayId == getExpandedDisplayId(mContext)) {
-                mBubbleData.setExpanded(false);
+                if (mImeVisible) {
+                    hideCurrentInputMethod();
+                } else {
+                    mBubbleData.setExpanded(false);
+                }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 7020f1c..acbde9f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -366,11 +366,15 @@
             validShortcutIds.add(info.getId());
         }
 
-        final Predicate<Bubble> invalidBubblesFromPackage = bubble ->
-                packageName.equals(bubble.getPackageName())
-                        && (bubble.getShortcutInfo() == null
-                            || !bubble.getShortcutInfo().isEnabled()
-                            || !validShortcutIds.contains(bubble.getShortcutInfo().getId()));
+        final Predicate<Bubble> invalidBubblesFromPackage = bubble -> {
+            final boolean bubbleIsFromPackage = packageName.equals(bubble.getPackageName());
+            final boolean hasShortcutIdAndValidShortcut =
+                    bubble.hasMetadataShortcutId()
+                            && bubble.getShortcutInfo() != null
+                            && bubble.getShortcutInfo().isEnabled()
+                            && validShortcutIds.contains(bubble.getShortcutInfo().getId());
+            return bubbleIsFromPackage && !hasShortcutIdAndValidShortcut;
+        };
 
         final Consumer<Bubble> removeBubble = bubble ->
                 dismissBubbleWithKey(bubble.getKey(), reason);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
index 390f706..db64a13 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
@@ -77,7 +77,7 @@
             BubbleEntity(
                     userId,
                     b.packageName,
-                    b.shortcutInfo?.id ?: return@mapNotNull null,
+                    b.metadataShortcutId ?: return@mapNotNull null,
                     b.key,
                     b.rawDesiredHeight,
                     b.rawDesiredHeightResId,
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 7c3e027..b34312e2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -173,7 +173,8 @@
                             return;
                         }
                         try {
-                            if (!mIsOverflow && mBubble.getShortcutInfo() != null) {
+                            if (!mIsOverflow && mBubble.hasMetadataShortcutId()
+                                    && mBubble.getShortcutInfo() != null) {
                                 options.setApplyActivityFlagsForBubbles(true);
                                 mActivityView.startShortcutActivity(mBubble.getShortcutInfo(),
                                         options, null /* sourceBounds */);
@@ -465,7 +466,6 @@
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        hideImeIfVisible();
         mKeyboardVisible = false;
         mNeedsNewHeight = false;
         if (mActivityView != null) {
@@ -616,7 +616,7 @@
 
             if (isNew) {
                 mPendingIntent = mBubble.getBubbleIntent();
-                if (mPendingIntent != null || mBubble.getShortcutInfo() != null) {
+                if (mPendingIntent != null || mBubble.hasMetadataShortcutId()) {
                     setContentVisibility(false);
                     mActivityView.setVisibility(VISIBLE);
                 }
@@ -788,7 +788,7 @@
     }
 
     private boolean usingActivityView() {
-        return (mPendingIntent != null || mBubble.getShortcutInfo() != null)
+        return (mPendingIntent != null || mBubble.hasMetadataShortcutId())
                 && mActivityView != null;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
index a888bd5..ffb650d6 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
@@ -70,9 +70,6 @@
 
     private static final String WHITELISTED_AUTO_BUBBLE_APPS = "whitelisted_auto_bubble_apps";
 
-    private static final String ALLOW_BUBBLE_OVERFLOW = "allow_bubble_overflow";
-    private static final boolean ALLOW_BUBBLE_OVERFLOW_DEFAULT = true;
-
     /**
      * When true, if a notification has the information necessary to bubble (i.e. valid
      * contentIntent and an icon or image), then a {@link android.app.Notification.BubbleMetadata}
@@ -87,15 +84,6 @@
     }
 
     /**
-     * When true, show a menu with dismissed and aged-out bubbles.
-     */
-    static boolean allowBubbleOverflow(Context context) {
-        return Settings.Secure.getInt(context.getContentResolver(),
-                ALLOW_BUBBLE_OVERFLOW,
-                ALLOW_BUBBLE_OVERFLOW_DEFAULT ? 1 : 0) != 0;
-    }
-
-    /**
      * Same as {@link #allowAnyNotifToBubble(Context)} except it filters for notifications that
      * are using {@link Notification.MessagingStyle} and have remote input.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 50828e8..09e8799 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -384,6 +384,11 @@
     public final Consumer<Boolean> mOnImeVisibilityChanged;
 
     /**
+     * Callback to run to ask BubbleController to hide the current IME.
+     */
+    private final Runnable mHideCurrentInputMethodCallback;
+
+    /**
      * The currently magnetized object, which is being dragged and will be attracted to the magnetic
      * dismiss target.
      *
@@ -560,7 +565,7 @@
                         mMagneticTarget,
                         mIndividualBubbleMagnetListener);
 
-                hideImeFromExpandedBubble();
+                hideCurrentInputMethod();
 
                 // Save the magnetized individual bubble so we can dispatch touch events to it.
                 mMagnetizedObject = mExpandedAnimationController.getMagnetizedBubbleDraggingOut();
@@ -732,7 +737,8 @@
             FloatingContentCoordinator floatingContentCoordinator,
             SysUiState sysUiState,
             Runnable allBubblesAnimatedOutAction,
-            Consumer<Boolean> onImeVisibilityChanged) {
+            Consumer<Boolean> onImeVisibilityChanged,
+            Runnable hideCurrentInputMethodCallback) {
         super(context);
 
         mBubbleData = data;
@@ -868,6 +874,7 @@
         setUpOverflow();
 
         mOnImeVisibilityChanged = onImeVisibilityChanged;
+        mHideCurrentInputMethodCallback = hideCurrentInputMethodCallback;
 
         setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
             onImeVisibilityChanged.accept(insets.getInsets(WindowInsets.Type.ime()).bottom > 0);
@@ -1145,9 +1152,6 @@
     }
 
     private void setUpOverflow() {
-        if (!BubbleExperimentConfig.allowBubbleOverflow(mContext)) {
-            return;
-        }
         int overflowBtnIndex = 0;
         if (mBubbleOverflow == null) {
             mBubbleOverflow = new BubbleOverflow(getContext());
@@ -1513,8 +1517,7 @@
     }
 
     private void updateOverflowVisibility() {
-        if (!BubbleExperimentConfig.allowBubbleOverflow(mContext)
-                || mBubbleOverflow == null) {
+        if (mBubbleOverflow == null) {
             return;
         }
         mBubbleOverflow.setVisible(mIsExpanded ? VISIBLE : GONE);
@@ -1593,6 +1596,8 @@
         updatePointerPosition();
 
         if (mIsExpanded) {
+            hideCurrentInputMethod();
+
             // Make the container of the expanded view transparent before removing the expanded view
             // from it. Otherwise a punch hole created by {@link android.view.SurfaceView} in the
             // expanded view becomes visible on the screen. See b/126856255
@@ -1601,11 +1606,6 @@
                 if (previouslySelected != null) {
                     previouslySelected.setContentVisibility(false);
                 }
-                if (previouslySelected != null && previouslySelected.getExpandedView() != null) {
-                    // Hide the currently expanded bubble's IME if it's visible before switching
-                    // to a new bubble.
-                    previouslySelected.getExpandedView().hideImeIfVisible();
-                }
 
                 updateExpandedBubble();
                 requestUpdate();
@@ -1633,6 +1633,8 @@
             return;
         }
 
+        hideCurrentInputMethod();
+
         mSysUiState
                 .setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED, shouldExpand)
                 .commitUpdate(mContext.getDisplayId());
@@ -1816,12 +1818,12 @@
         }
     }
 
-    void hideImeFromExpandedBubble() {
-        if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
-            // Hide the currently expanded bubble's IME if it's visible before switching to a new
-            // bubble.
-            mExpandedBubble.getExpandedView().hideImeIfVisible();
-        }
+    /**
+     * Asks the BubbleController to hide the IME from anywhere, whether it's focused on Bubbles or
+     * not.
+     */
+    void hideCurrentInputMethod() {
+        mHideCurrentInputMethodCallback.run();
     }
 
     private void beforeExpandedViewAnimation() {
@@ -1898,7 +1900,7 @@
                             AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
                             mScaleInSpringConfig)
                     .addUpdateListener((target, values) -> {
-                        if (mExpandedBubble.getIconView() == null) {
+                        if (mExpandedBubble == null || mExpandedBubble.getIconView() == null) {
                             return;
                         }
                         mExpandedViewContainerMatrix.postTranslate(
@@ -1938,10 +1940,6 @@
         mAnimatingOutSurfaceContainer.setScaleX(0f);
         mAnimatingOutSurfaceContainer.setScaleY(0f);
 
-        if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
-            mExpandedBubble.getExpandedView().hideImeIfVisible();
-        }
-
         // Let the expanded animation controller know that it shouldn't animate child adds/reorders
         // since we're about to animate collapsed.
         mExpandedAnimationController.notifyPreparingToCollapse();
@@ -2776,11 +2774,8 @@
      * @return the number of bubbles in the stack view.
      */
     public int getBubbleCount() {
-        if (BubbleExperimentConfig.allowBubbleOverflow(mContext)) {
-            // Subtract 1 for the overflow button that is always in the bubble container.
-            return mBubbleContainer.getChildCount() - 1;
-        }
-        return mBubbleContainer.getChildCount();
+        // Subtract 1 for the overflow button that is always in the bubble container.
+        return mBubbleContainer.getChildCount() - 1;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/AnimatableScaleMatrix.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/AnimatableScaleMatrix.java
index ae78336..07acb71 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/AnimatableScaleMatrix.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/AnimatableScaleMatrix.java
@@ -134,4 +134,11 @@
     public float getPivotY() {
         return mPivotY;
     }
+
+    @Override
+    public boolean equals(Object obj) {
+        // Use object equality to allow this matrix to be used as a map key (which is required for
+        // PhysicsAnimator's animator caching).
+        return obj == this;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 416c81a..5052386 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -432,12 +432,13 @@
             }
         }
 
-        val resumeAction: Runnable? = mediaEntries.get(key)?.resumeAction
-        val hasCheckedForResume = mediaEntries.get(key)?.hasCheckedForResume == true
         foregroundExecutor.execute {
+            val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
+            val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
+            val active = mediaEntries[key]?.active ?: true
             onMediaDataLoaded(key, oldKey, MediaData(true, bgColor, app, smallIconDrawable, artist,
                     song, artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token,
-                    notif.contentIntent, null, active = true, resumeAction = resumeAction,
+                    notif.contentIntent, null, active, resumeAction = resumeAction,
                     notificationKey = key, hasCheckedForResume = hasCheckedForResume))
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
index cf8a636..9a134db 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
@@ -71,6 +71,7 @@
     ) : MediaController.Callback() {
 
         var timedOut = false
+        private var playing: Boolean? = null
 
         // Resume controls may have null token
         private val mediaController = if (data.token != null) {
@@ -94,7 +95,13 @@
                 Log.v(TAG, "onPlaybackStateChanged: $state")
             }
 
-            if (state == null || !isPlayingState(state.state)) {
+            val isPlaying = state != null && isPlayingState(state.state)
+            if (playing == isPlaying && playing != null) {
+                return
+            }
+            playing = isPlaying
+
+            if (!isPlaying) {
                 if (DEBUG) {
                     Log.v(TAG, "schedule timeout for $key")
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index d924346..e2feb71 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -273,8 +273,7 @@
             sendOnPipTransitionStarted(direction);
             // Don't bother doing an animation if the display rotation differs or if it's in
             // a non-supported windowing mode
-            wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
-            wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
+            applyWindowingModeChangeOnExit(wct, direction);
             WindowOrganizer.applyTransaction(wct);
             // Send finished callback though animation is ignored.
             sendOnPipTransitionFinished(direction);
@@ -303,6 +302,16 @@
         mExitingPip = true;
     }
 
+    private void applyWindowingModeChangeOnExit(WindowContainerTransaction wct, int direction) {
+        // Reset the final windowing mode.
+        wct.setWindowingMode(mToken, getOutPipWindowingMode());
+        // Simply reset the activity mode set prior to the animation running.
+        wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
+        if (mSplitDivider != null && direction == TRANSITION_DIRECTION_TO_SPLIT_SCREEN) {
+            wct.reparent(mToken, mSplitDivider.getSecondaryRoot(), true /* onTop */);
+        }
+    }
+
     /**
      * Removes PiP immediately.
      */
@@ -745,13 +754,7 @@
             // on the task to ensure that the task "matches" the parent's bounds.
             taskBounds = (direction == TRANSITION_DIRECTION_TO_FULLSCREEN)
                     ? null : destinationBounds;
-            // Reset the final windowing mode.
-            wct.setWindowingMode(mToken, getOutPipWindowingMode());
-            // Simply reset the activity mode set prior to the animation running.
-            wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
-            if (mSplitDivider != null && direction == TRANSITION_DIRECTION_TO_SPLIT_SCREEN) {
-                wct.reparent(mToken, mSplitDivider.getSecondaryRoot(), true /* onTop */);
-            }
+            applyWindowingModeChangeOnExit(wct, direction);
         } else {
             // Just a resize in PIP
             taskBounds = destinationBounds;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 87faacc..795d062 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -14,8 +14,8 @@
 
 package com.android.systemui.qs.tileimpl;
 
-import static androidx.lifecycle.Lifecycle.State.DESTROYED;
 import static androidx.lifecycle.Lifecycle.State.RESUMED;
+import static androidx.lifecycle.Lifecycle.State.STARTED;
 
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_CLICK;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_LONG_PRESS;
@@ -432,17 +432,19 @@
     }
 
     private void handleSetListeningInternal(Object listener, boolean listening) {
+        // This should be used to go from resumed to paused. Listening for ON_RESUME and ON_PAUSE
+        // in this lifecycle will determine the listening window.
         if (listening) {
             if (mListeners.add(listener) && mListeners.size() == 1) {
                 if (DEBUG) Log.d(TAG, "handleSetListening true");
-                mLifecycle.markState(RESUMED);
+                mLifecycle.setCurrentState(RESUMED);
                 handleSetListening(listening);
                 refreshState(); // Ensure we get at least one refresh after listening.
             }
         } else {
             if (mListeners.remove(listener) && mListeners.size() == 0) {
                 if (DEBUG) Log.d(TAG, "handleSetListening false");
-                mLifecycle.markState(DESTROYED);
+                mLifecycle.setCurrentState(STARTED);
                 handleSetListening(listening);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index e3fbdbc..468b9b1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -281,8 +281,10 @@
                 Intent.createChooser(sharingIntent, null, chooserAction.getIntentSender())
                         .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
                         .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, requestCode,
-                sharingChooserIntent, 0, null, UserHandle.CURRENT);
+
+        // cancel current pending intent (if any) since clipData isn't used for matching
+        PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0,
+                sharingChooserIntent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
 
         // Create a share action for the notification
         PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode,
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index ad31220..8a38199 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -253,7 +253,9 @@
             mSplitLayout.mSecondary = new Rect(mRotateSplitLayout.mSecondary);
             mRotateSplitLayout = null;
         }
-        update(newConfig);
+        if (isSplitActive()) {
+            update(newConfig);
+        }
     }
 
     Handler getHandler() {
@@ -328,11 +330,6 @@
         mHandler.post(this::removeDivider);
     }
 
-    void onTasksReady() {
-        mHandler.post(() -> update(mDisplayController.getDisplayContext(
-                mContext.getDisplayId()).getResources().getConfiguration()));
-    }
-
     private void updateVisibility(final boolean visible) {
         if (DEBUG) Slog.d(TAG, "Updating visibility " + mVisible + "->" + visible);
         if (mVisible != visible) {
@@ -534,7 +531,7 @@
 
     void ensureMinimizedSplit() {
         setHomeMinimized(true /* minimized */, mHomeStackResizable);
-        if (!isDividerVisible()) {
+        if (mView != null && !isDividerVisible()) {
             // Wasn't in split-mode yet, so enter now.
             if (DEBUG) {
                 Slog.d(TAG, " entering split mode with minimized=true");
@@ -545,7 +542,7 @@
 
     void ensureNormalSplit() {
         setHomeMinimized(false /* minimized */, mHomeStackResizable);
-        if (!isDividerVisible()) {
+        if (mView != null && !isDividerVisible()) {
             // Wasn't in split-mode, so enter now.
             if (DEBUG) {
                 Slog.d(TAG, " enter split mode unminimized ");
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
index 6751e8d..4a2cad7 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
@@ -113,8 +113,6 @@
                 t.setColor(mSecondaryDim, new float[]{0f, 0f, 0f});
                 t.apply();
                 releaseTransaction(t);
-
-                mDivider.onTasksReady();
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index 5926a5b..304fe00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -121,6 +121,8 @@
 
     private final Context mContext;
     private final OverviewProxyService mOverviewProxyService;
+    private final Runnable mStateChangeCallback;
+
     private final PluginManager mPluginManager;
     // Activities which should not trigger Back gesture.
     private final List<ComponentName> mGestureBlockingActivities = new ArrayList<>();
@@ -196,13 +198,15 @@
             };
 
     public EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService,
-            SysUiState sysUiFlagContainer, PluginManager pluginManager) {
+            SysUiState sysUiFlagContainer, PluginManager pluginManager,
+            Runnable stateChangeCallback) {
         super(Dependency.get(BroadcastDispatcher.class));
         mContext = context;
         mDisplayId = context.getDisplayId();
         mMainExecutor = context.getMainExecutor();
         mOverviewProxyService = overviewProxyService;
         mPluginManager = pluginManager;
+        mStateChangeCallback = stateChangeCallback;
         ComponentName recentsComponentName = ComponentName.unflattenFromString(
                 context.getString(com.android.internal.R.string.config_recentsComponentName));
         if (recentsComponentName != null) {
@@ -226,13 +230,13 @@
                 Log.e(TAG, "Failed to add gesture blocking activities", e);
             }
         }
-        Dependency.get(ProtoTracer.class).add(this);
 
+        Dependency.get(ProtoTracer.class).add(this);
         mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT,
                 ViewConfiguration.getLongPressTimeout());
 
         mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(
-                mContext.getMainThreadHandler(), mContext, this::updateCurrentUserResources);
+                mContext.getMainThreadHandler(), mContext, this::onNavigationSettingsChanged);
 
         updateCurrentUserResources();
         sysUiFlagContainer.addCallback(sysUiFlags -> mSysUiFlags = sysUiFlags);
@@ -263,6 +267,14 @@
         mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop() * backGestureSlop;
     }
 
+    private void onNavigationSettingsChanged() {
+        boolean wasBackAllowed = isHandlingGestures();
+        updateCurrentUserResources();
+        if (wasBackAllowed != isHandlingGestures()) {
+            mStateChangeCallback.run();
+        }
+    }
+
     @Override
     public void onUserSwitched(int newUserId) {
         updateIsEnabled();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 063305e..27daf86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -25,6 +25,7 @@
 import static android.view.InsetsState.containsType;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
 
@@ -219,12 +220,13 @@
      * original handle hidden and we'll flip the visibilities once the
      * {@link #mTasksFrozenListener} fires
      */
-    private VerticalNavigationHandle mOrientationHandle;
+    private QuickswitchOrientedNavHandle mOrientationHandle;
     private WindowManager.LayoutParams mOrientationParams;
     private int mStartingQuickSwitchRotation = -1;
     private int mCurrentRotation;
     private ViewTreeObserver.OnGlobalLayoutListener mOrientationHandleGlobalLayoutListener;
     private UiEventLogger mUiEventLogger;
+    private boolean mShowOrientedHandleForImmersiveMode;
 
     @com.android.internal.annotations.VisibleForTesting
     public enum NavBarActionEvent implements UiEventLogger.UiEventEnum {
@@ -296,6 +298,9 @@
         @Override
         public void onQuickSwitchToNewTask(@Surface.Rotation int rotation) {
             mStartingQuickSwitchRotation = rotation;
+            if (rotation == -1) {
+                mShowOrientedHandleForImmersiveMode = false;
+            }
             orientSecondaryHomeHandle();
         }
 
@@ -585,7 +590,7 @@
         getContext().getSystemService(DisplayManager.class)
                 .registerDisplayListener(this, new Handler(Looper.getMainLooper()));
 
-        mOrientationHandle = new VerticalNavigationHandle(getContext());
+        mOrientationHandle = new QuickswitchOrientedNavHandle(getContext());
 
         getBarTransitions().addDarkIntensityListener(mOrientationHandleIntensityListener);
         mOrientationParams = new WindowManager.LayoutParams(0, 0,
@@ -596,8 +601,11 @@
                         | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                         | WindowManager.LayoutParams.FLAG_SLIPPERY,
                 PixelFormat.TRANSLUCENT);
+        mOrientationParams.setTitle("SecondaryHomeHandle" + getContext().getDisplayId());
+        mOrientationParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
         mWindowManager.addView(mOrientationHandle, mOrientationParams);
         mOrientationHandle.setVisibility(View.GONE);
+        mOrientationParams.setFitInsetsTypes(0 /* types*/);
         mOrientationHandleGlobalLayoutListener =
                 () -> {
                     if (mStartingQuickSwitchRotation == -1) {
@@ -634,22 +642,28 @@
             int height = 0;
             int width = 0;
             Rect dispSize = mWindowManager.getCurrentWindowMetrics().getBounds();
+            mOrientationHandle.setDeltaRotation(deltaRotation);
             switch (deltaRotation) {
                 case Surface.ROTATION_90:
                 case Surface.ROTATION_270:
                     height = dispSize.height();
-                    width = getResources()
-                            .getDimensionPixelSize(R.dimen.navigation_bar_height);
+                    width = mNavigationBarView.getHeight();
                     break;
                 case Surface.ROTATION_180:
                 case Surface.ROTATION_0:
                     // TODO(b/152683657): Need to determine best UX for this
-                    resetSecondaryHandle();
-                    return;
+                    if (!mShowOrientedHandleForImmersiveMode) {
+                        resetSecondaryHandle();
+                        return;
+                    }
+                    width = dispSize.width();
+                    height = mNavigationBarView.getHeight();
+                    break;
             }
 
             mOrientationParams.gravity =
-                    deltaRotation == Surface.ROTATION_90 ? Gravity.LEFT : Gravity.RIGHT;
+                    deltaRotation == Surface.ROTATION_0 ? Gravity.BOTTOM :
+                            (deltaRotation == Surface.ROTATION_90 ? Gravity.LEFT : Gravity.RIGHT);
             mOrientationParams.height = height;
             mOrientationParams.width = width;
             mWindowManager.updateViewLayout(mOrientationHandle, mOrientationParams);
@@ -743,6 +757,11 @@
                 && mNavigationBarWindowState != state) {
             mNavigationBarWindowState = state;
             updateSystemUiStateFlags(-1);
+            mShowOrientedHandleForImmersiveMode = state == WINDOW_STATE_HIDDEN;
+            if (mOrientationHandle != null
+                    && mStartingQuickSwitchRotation != -1) {
+                orientSecondaryHomeHandle();
+            }
             if (DEBUG_WINDOW_STATE) Log.d(TAG, "Navigation bar " + windowStateToString(state));
 
             if (mNavigationBarView != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index dbff643..1eab427 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -326,8 +326,8 @@
 
         mNavColorSampleMargin = getResources()
                         .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
-        mEdgeBackGestureHandler = new EdgeBackGestureHandler(
-                context, mOverviewProxyService, mSysUiFlagContainer, mPluginManager);
+        mEdgeBackGestureHandler = new EdgeBackGestureHandler(context, mOverviewProxyService,
+                mSysUiFlagContainer, mPluginManager, this::updateStates);
         mRegionSamplingHelper = new RegionSamplingHelper(this,
                 new RegionSamplingHelper.SamplingCallback() {
                     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickswitchOrientedNavHandle.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickswitchOrientedNavHandle.java
new file mode 100644
index 0000000..fe74677
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickswitchOrientedNavHandle.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import android.view.Surface;
+
+import com.android.systemui.R;
+
+/** Temporarily shown view when using QuickSwitch to switch between apps of different rotations */
+public class QuickswitchOrientedNavHandle extends NavigationHandle {
+    private final int mWidth;
+    private final RectF mTmpBoundsRectF = new RectF();
+    private @Surface.Rotation int mDeltaRotation;
+
+    public QuickswitchOrientedNavHandle(Context context) {
+        super(context);
+        mWidth = context.getResources().getDimensionPixelSize(R.dimen.navigation_home_handle_width);
+    }
+
+    void setDeltaRotation(@Surface.Rotation int rotation) {
+        mDeltaRotation = rotation;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        canvas.drawRoundRect(computeHomeHandleBounds(), mRadius, mRadius, mPaint);
+    }
+
+    RectF computeHomeHandleBounds() {
+        int left;
+        int top;
+        int bottom;
+        int right;
+        int radiusOffset = mRadius * 2;
+        int topStart = getLocationOnScreen()[1];
+
+        switch (mDeltaRotation) {
+            default:
+            case Surface.ROTATION_0:
+            case Surface.ROTATION_180:
+                int height = mRadius * 2;
+                left = getWidth() / 2 - mWidth / 2;
+                top = (getHeight() - mBottom - height);
+                right = getWidth() / 2 + mWidth / 2;
+                bottom = top + height;
+                break;
+            case Surface.ROTATION_90:
+                left = mBottom;
+                right = left + radiusOffset;
+                top = getHeight() / 2 - (mWidth / 2) - (topStart / 2);
+                bottom = top + mWidth;
+                break;
+            case Surface.ROTATION_270:
+                right = getWidth() - mBottom;
+                left = right - radiusOffset;
+                top = getHeight() / 2 - (mWidth / 2) - (topStart / 2);
+                bottom = top + mWidth;
+                break;
+        }
+        mTmpBoundsRectF.set(left, top, right, bottom);
+        return mTmpBoundsRectF;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/VerticalNavigationHandle.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/VerticalNavigationHandle.java
deleted file mode 100644
index 0cdf1d3..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/VerticalNavigationHandle.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.RectF;
-
-import com.android.systemui.R;
-
-/** Temporarily shown view when using QuickSwitch to switch between apps of different rotations */
-public class VerticalNavigationHandle extends NavigationHandle {
-    private final int mWidth;
-    private final RectF mTmpBoundsRectF = new RectF();
-
-    public VerticalNavigationHandle(Context context) {
-        super(context);
-        mWidth = context.getResources().getDimensionPixelSize(R.dimen.navigation_home_handle_width);
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        canvas.drawRoundRect(computeHomeHandleBounds(), mRadius, mRadius, mPaint);
-    }
-
-    RectF computeHomeHandleBounds() {
-        int left;
-        int top;
-        int bottom;
-        int right;
-        int topStart = getLocationOnScreen()[1];
-        int radiusOffset = mRadius * 2;
-        right = getWidth() - mBottom;
-        top = getHeight() / 2 - (mWidth / 2) - (topStart / 2);
-        left = getWidth() - mBottom - radiusOffset;
-        bottom = top + mWidth;
-        mTmpBoundsRectF.set(left, top, right, bottom);
-        return mTmpBoundsRectF;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
index 6794a2a..52d4647 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -253,7 +253,7 @@
         private final AtomicBoolean mRegistered = new AtomicBoolean();
 
         @Inject
-        public ProximityCheck(ProximitySensor sensor, DelayableExecutor delayableExecutor) {
+        public ProximityCheck(ProximitySensor sensor, @Main DelayableExecutor delayableExecutor) {
             mSensor = sensor;
             mSensor.setTag("prox_check");
             mDelayableExecutor = delayableExecutor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
index 8b254e3..58959c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
@@ -18,6 +18,7 @@
 
 import static android.view.WindowInsets.Type.ime;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -39,6 +40,7 @@
 
 import com.android.systemui.SysuiTestCase;
 
+import org.junit.After;
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -52,6 +54,11 @@
     public ActivityTestRule<TestActivity> mActivityTestRule = new ActivityTestRule<>(
             TestActivity.class, false, false);
 
+    @After
+    public void tearDown() {
+        executeShellCommand("input keyevent HOME");
+    }
+
     /**
      * This test verifies that GlobalActions, which is frequently used to capture bugreports,
      * doesn't interfere with the IME, i.e. soft-keyboard state.
@@ -68,6 +75,9 @@
             waitUntil("Ime is not visible", activity::isImeVisible);
         }
 
+        // In some cases, IME is not controllable. e.g., floating IME or fullscreen IME.
+        final boolean activityControlledIme = activity.mControlsIme;
+
         executeShellCommand("input keyevent --longpress POWER");
 
         waitUntil("activity loses focus", () -> !activity.mHasFocus);
@@ -77,9 +87,9 @@
 
         runAssertionOnMainThread(() -> {
             assertTrue("IME should remain visible behind GlobalActions, but didn't",
-                    activity.mControlsIme);
-            assertTrue("App behind GlobalActions should remain in control of IME, but didn't",
                     activity.mImeVisible);
+            assertEquals("App behind GlobalActions should remain in control of IME, but didn't",
+                    activityControlledIme, activity.mControlsIme);
         });
     }
 
@@ -181,7 +191,7 @@
         }
 
         boolean isImeVisible() {
-            return mHasFocus && mControlsIme && mImeVisible;
+            return mHasFocus && mImeVisible;
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index 20a6da5..e56bbabfd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -15,6 +15,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.SbnBuilder
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
@@ -26,8 +27,8 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
 
 private const val KEY = "KEY"
 private const val PACKAGE_NAME = "com.android.systemui"
@@ -35,7 +36,6 @@
 private const val SESSION_ARTIST = "artist"
 private const val SESSION_TITLE = "title"
 
-private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
 private fun <T> anyObject(): T {
     return Mockito.anyObject<T>()
 }
@@ -104,6 +104,19 @@
     }
 
     @Test
+    fun testOnSwipeToDismiss_deactivatesMedia() {
+        val data = MediaData(initialized = true, backgroundColor = 0, app = null, appIcon = null,
+                artist = null, song = null, artwork = null, actions = emptyList(),
+                actionsToShowInCompact = emptyList(), packageName = "INVALID", token = null,
+                clickIntent = null, device = null, active = true, resumeAction = null)
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
+
+        mediaDataManager.onSwipeToDismiss()
+        assertThat(data.active).isFalse()
+    }
+
+    @Test
     fun testLoadsMetadataOnBackground() {
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         assertThat(backgroundExecutor.numPending()).isEqualTo(1)
@@ -119,6 +132,30 @@
     }
 
     @Test
+    fun testOnMetaDataLoaded_conservesActiveFlag() {
+        val listener = TestListener()
+        whenever(mediaControllerFactory.create(anyObject())).thenReturn(controller)
+        whenever(controller.metadata).thenReturn(metadataBuilder.build())
+        mediaDataManager.addListener(listener)
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(listener.data!!.active).isTrue()
+
+        // Swiping away makes the notification not active
+        mediaDataManager.onSwipeToDismiss()
+        assertThat(mediaDataManager.hasActiveMedia()).isFalse()
+
+        // And when a notification is updated
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+
+        // MediaData should still be inactive
+        assertThat(mediaDataManager.hasActiveMedia()).isFalse()
+    }
+
+    @Test
     fun testHasAnyMedia_whenAddingMedia() {
         assertThat(mediaDataManager.hasAnyMedia()).isFalse()
         val data = mock(MediaData::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 1c0d451..438de99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -14,6 +14,10 @@
 
 package com.android.systemui.qs.tileimpl;
 
+
+import static androidx.lifecycle.Lifecycle.State.DESTROYED;
+import static androidx.lifecycle.Lifecycle.State.RESUMED;
+
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_CLICK;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_LONG_PRESS;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_SECONDARY_CLICK;
@@ -23,6 +27,9 @@
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -267,6 +274,42 @@
         verify(mQsLogger).logTileChangeListening(SPEC, false);
     }
 
+    @Test
+    public void testListeningTrue_stateAtLeastResumed() {
+        mTile.setListening(new Object(), true); // Listen with some object
+
+        TestableLooper.get(this).processAllMessages();
+
+        assertTrue(mTile.getLifecycle().getCurrentState().isAtLeast(RESUMED));
+    }
+
+    @Test
+    public void testTileDoesntStartResumed() {
+        assertFalse(mTile.getLifecycle().getCurrentState().isAtLeast(RESUMED));
+    }
+
+    @Test
+    public void testListeningFalse_stateAtMostCreated() {
+        Object o = new Object();
+        mTile.setListening(o, true);
+
+        mTile.setListening(o, false);
+
+        TestableLooper.get(this).processAllMessages();
+        assertFalse(mTile.getLifecycle().getCurrentState().isAtLeast(RESUMED));
+    }
+
+    @Test
+    public void testListeningFalse_stateNotDestroyed() {
+        Object o = new Object();
+        mTile.setListening(o, true);
+
+        mTile.setListening(o, false);
+
+        TestableLooper.get(this).processAllMessages();
+        assertNotEquals(DESTROYED, mTile.getLifecycle().getCurrentState());
+    }
+
     private void assertEvent(UiEventLogger.UiEventEnum eventType,
             UiEventLoggerFake.FakeUiEvent fakeEvent) {
         assertEquals(eventType.getId(), fakeEvent.eventId);
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index cc095a0..4f053cb 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -171,6 +171,14 @@
      */
     public static final int TETHERING_ETHERNET = 5;
 
+    /**
+     * WIGIG tethering type. Use a separate type to prevent
+     * conflicts with TETHERING_WIFI
+     * This type is only used internally by the tethering module
+     * @hide
+     */
+    public static final int TETHERING_WIGIG = 6;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(value = {
diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml
index 9269c6f..9b9dcde 100644
--- a/packages/Tethering/res/values/config.xml
+++ b/packages/Tethering/res/values/config.xml
@@ -43,6 +43,13 @@
     </string-array>
 
     <!-- List of regexpressions describing the interface (if any) that represent tetherable
+         WiGig interfaces.  If the device doesn't want to support tethering over WiGig this
+         should be empty.  An example would be "wigig\\d" -->
+    <string-array translatable="false" name="config_tether_wigig_regexs">
+        <item>"wigig\\d"</item>
+    </string-array>
+
+    <!-- List of regexpressions describing the interface (if any) that represent tetherable
          Wifi P2P interfaces.  If the device doesn't want to support tethering over Wifi P2p this
          should be empty.  An example would be "p2p-p2p\\d-.*" -->
     <string-array translatable="false" name="config_tether_wifi_p2p_regexs">
diff --git a/packages/Tethering/res/values/overlayable.xml b/packages/Tethering/res/values/overlayable.xml
index 4e2bb1e..6a33d55 100644
--- a/packages/Tethering/res/values/overlayable.xml
+++ b/packages/Tethering/res/values/overlayable.xml
@@ -20,6 +20,7 @@
             <item type="array" name="config_tether_usb_regexs"/>
             <item type="array" name="config_tether_ncm_regexs" />
             <item type="array" name="config_tether_wifi_regexs"/>
+            <item type="array" name="config_tether_wigig_regexs"/>
             <item type="array" name="config_tether_wifi_p2p_regexs"/>
             <item type="array" name="config_tether_bluetooth_regexs"/>
             <item type="array" name="config_tether_dhcp_range"/>
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index 35c1563..8af1797 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -617,7 +617,8 @@
         final Boolean setIfaceUp;
         if (mInterfaceType == TetheringManager.TETHERING_WIFI
                 || mInterfaceType == TetheringManager.TETHERING_WIFI_P2P
-                || mInterfaceType == TetheringManager.TETHERING_ETHERNET) {
+                || mInterfaceType == TetheringManager.TETHERING_ETHERNET
+                || mInterfaceType == TetheringManager.TETHERING_WIGIG) {
             // The WiFi and Ethernet stack has ownership of the interface up/down state.
             // It is unclear whether the Bluetooth or USB stacks will manage their own
             // state.
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
index fe92204..33b9d00 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
@@ -16,8 +16,11 @@
 
 package com.android.networkstack.tethering;
 
+import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
 import static android.net.util.TetheringUtils.uint16;
 
+import android.annotation.NonNull;
 import android.hardware.tetheroffload.config.V1_0.IOffloadConfig;
 import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
 import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback;
@@ -25,6 +28,7 @@
 import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
 import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
 import android.net.netlink.NetlinkSocket;
+import android.net.netlink.StructNlMsgHdr;
 import android.net.util.SharedLog;
 import android.net.util.SocketUtils;
 import android.os.Handler;
@@ -37,9 +41,11 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
 import java.io.IOException;
 import java.net.SocketAddress;
 import java.net.SocketException;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.NoSuchElementException;
 
@@ -63,6 +69,11 @@
     private static final int NF_NETLINK_CONNTRACK_NEW = 1;
     private static final int NF_NETLINK_CONNTRACK_UPDATE = 2;
     private static final int NF_NETLINK_CONNTRACK_DESTROY = 4;
+    // Reference libnetfilter_conntrack/linux_nfnetlink_conntrack.h
+    public static final short NFNL_SUBSYS_CTNETLINK = 1;
+    public static final short IPCTNL_MSG_CT_GET = 1;
+
+    private final long NETLINK_MESSAGE_TIMEOUT_MS = 500;
 
     private final Handler mHandler;
     private final SharedLog mLog;
@@ -226,6 +237,9 @@
                 NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY);
         if (h1 == null) return false;
 
+        sendNetlinkMessage(h1, (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET),
+                           (short) (NLM_F_REQUEST | NLM_F_DUMP));
+
         final NativeHandle h2 = mDeps.createConntrackSocket(
                 NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY);
         if (h2 == null) {
@@ -252,6 +266,25 @@
         return results.mSuccess;
     }
 
+    @VisibleForTesting
+    public void sendNetlinkMessage(@NonNull NativeHandle handle, short type, short flags) {
+        final int length = StructNlMsgHdr.STRUCT_SIZE;
+        final byte[] msg = new byte[length];
+        final StructNlMsgHdr nlh = new StructNlMsgHdr();
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(msg);
+        nlh.nlmsg_len = length;
+        nlh.nlmsg_type = type;
+        nlh.nlmsg_flags = flags;
+        nlh.nlmsg_seq = 1;
+        nlh.pack(byteBuffer);
+        try {
+            NetlinkSocket.sendMessage(handle.getFileDescriptor(), msg, 0 /* offset */, length,
+                                      NETLINK_MESSAGE_TIMEOUT_MS);
+        } catch (ErrnoException | InterruptedIOException e) {
+            mLog.e("Unable to send netfilter message, error: " + e);
+        }
+    }
+
     private void closeFdInNativeHandle(final NativeHandle h) {
         try {
             h.close();
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
index c72ac52..3695ec6 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -40,6 +40,7 @@
 import static android.net.TetheringManager.TETHERING_USB;
 import static android.net.TetheringManager.TETHERING_WIFI;
 import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHERING_WIGIG;
 import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
 import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
 import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
@@ -495,7 +496,8 @@
             if (up) {
                 maybeTrackNewInterfaceLocked(iface);
             } else {
-                if (ifaceNameToType(iface) == TETHERING_BLUETOOTH) {
+                if (ifaceNameToType(iface) == TETHERING_BLUETOOTH
+                        || ifaceNameToType(iface) == TETHERING_WIGIG) {
                     stopTrackingInterfaceLocked(iface);
                 } else {
                     // Ignore usb0 down after enabling RNDIS.
@@ -517,6 +519,8 @@
 
         if (cfg.isWifi(iface)) {
             return TETHERING_WIFI;
+        } else if (cfg.isWigig(iface)) {
+            return TETHERING_WIGIG;
         } else if (cfg.isWifiP2p(iface)) {
             return TETHERING_WIFI_P2P;
         } else if (cfg.isUsb(iface)) {
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index 18b2b78..e1771a5 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -92,6 +92,7 @@
 
     public final String[] tetherableUsbRegexs;
     public final String[] tetherableWifiRegexs;
+    public final String[] tetherableWigigRegexs;
     public final String[] tetherableWifiP2pRegexs;
     public final String[] tetherableBluetoothRegexs;
     public final String[] tetherableNcmRegexs;
@@ -125,6 +126,7 @@
         // us an interface name. Careful consideration needs to be given to
         // implications for Settings and for provisioning checks.
         tetherableWifiRegexs = getResourceStringArray(res, R.array.config_tether_wifi_regexs);
+        tetherableWigigRegexs = getResourceStringArray(res, R.array.config_tether_wigig_regexs);
         tetherableWifiP2pRegexs = getResourceStringArray(
                 res, R.array.config_tether_wifi_p2p_regexs);
         tetherableBluetoothRegexs = getResourceStringArray(
@@ -167,6 +169,11 @@
         return matchesDownstreamRegexs(iface, tetherableWifiRegexs);
     }
 
+    /** Check whether input interface belong to wigig.*/
+    public boolean isWigig(String iface) {
+        return matchesDownstreamRegexs(iface, tetherableWigigRegexs);
+    }
+
     /** Check whether this interface is Wifi P2P interface. */
     public boolean isWifiP2p(String iface) {
         return matchesDownstreamRegexs(iface, tetherableWifiP2pRegexs);
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
index f8ff1cb..c543fad 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
@@ -17,13 +17,17 @@
 package com.android.networkstack.tethering;
 
 import static android.net.util.TetheringUtils.uint16;
+import static android.system.OsConstants.SOCK_STREAM;
+import static android.system.OsConstants.AF_UNIX;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.hardware.tetheroffload.config.V1_0.IOffloadConfig;
 import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
@@ -31,11 +35,14 @@
 import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
 import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
 import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
+import android.net.netlink.StructNlMsgHdr;
 import android.net.util.SharedLog;
 import android.os.Handler;
 import android.os.NativeHandle;
 import android.os.test.TestLooper;
+import android.system.ErrnoException;
 import android.system.OsConstants;
+import android.system.Os;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -47,6 +54,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.FileDescriptor;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 
 @RunWith(AndroidJUnit4.class)
@@ -64,6 +74,10 @@
     @Mock private IOffloadControl mIOffloadControl;
     @Mock private NativeHandle mNativeHandle;
 
+    // Random values to test Netlink message.
+    private static final short TEST_TYPE = 184;
+    private static final short TEST_FLAGS = 263;
+
     class MyDependencies extends OffloadHardwareInterface.Dependencies {
         MyDependencies(SharedLog log) {
             super(log);
@@ -203,6 +217,31 @@
                 eq(uint16(udpParams.dst.port)));
     }
 
+    @Test
+    public void testNetlinkMessage() throws Exception {
+        FileDescriptor writeSocket = new FileDescriptor();
+        FileDescriptor readSocket = new FileDescriptor();
+        try {
+            Os.socketpair(AF_UNIX, SOCK_STREAM, 0, writeSocket, readSocket);
+        } catch (ErrnoException e) {
+            fail();
+            return;
+        }
+        when(mNativeHandle.getFileDescriptor()).thenReturn(writeSocket);
+
+        mOffloadHw.sendNetlinkMessage(mNativeHandle, TEST_TYPE, TEST_FLAGS);
+
+        ByteBuffer buffer = ByteBuffer.allocate(StructNlMsgHdr.STRUCT_SIZE);
+        int read = Os.read(readSocket, buffer);
+
+        buffer.flip();
+        assertEquals(StructNlMsgHdr.STRUCT_SIZE, buffer.getInt());
+        assertEquals(TEST_TYPE, buffer.getShort());
+        assertEquals(TEST_FLAGS, buffer.getShort());
+        assertEquals(1 /* seq */, buffer.getInt());
+        assertEquals(0 /* pid */, buffer.getInt());
+    }
+
     private NatTimeoutUpdate buildNatTimeoutUpdate(final int proto) {
         final NatTimeoutUpdate params = new NatTimeoutUpdate();
         params.proto = proto;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 7f912a4..d2b1bd1 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -56,6 +56,8 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.database.ContentObserver;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.display.DisplayManager;
 import android.hardware.fingerprint.IFingerprintService;
@@ -190,6 +192,9 @@
     private final SimpleStringSplitter mStringColonSplitter =
             new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
 
+    private final Rect mTempRect = new Rect();
+    private final Rect mTempRect1 = new Rect();
+
     private final PackageManager mPackageManager;
 
     private final PowerManager mPowerManager;
@@ -246,6 +251,7 @@
     //TODO: Remove this hack
     private boolean mInitialized;
 
+    private Point mTempPoint;
     private boolean mIsAccessibilityButtonShown;
 
     private AccessibilityUserState getCurrentUserStateLocked() {
@@ -1068,6 +1074,18 @@
     }
 
     /**
+     * Gets a point within the accessibility focused node where we can send down
+     * and up events to perform a click.
+     *
+     * @param outPoint The click point to populate.
+     * @return Whether accessibility a click point was found and set.
+     */
+    // TODO: (multi-display) Make sure this works for multiple displays.
+    public boolean getAccessibilityFocusClickPointInScreen(Point outPoint) {
+        return getInteractionBridge().getAccessibilityFocusClickPointInScreenNotLocked(outPoint);
+    }
+
+    /**
      * Perform an accessibility action on the view that currently has accessibility focus.
      * Has no effect if no item has accessibility focus, if the item with accessibility
      * focus does not expose the specified action, or if the action fails.
@@ -1081,6 +1099,32 @@
         return getInteractionBridge().performActionOnAccessibilityFocusedItemNotLocked(action);
     }
 
+    /**
+     * Returns true if accessibility focus is confined to the active window.
+     */
+    public boolean accessibilityFocusOnlyInActiveWindow() {
+        synchronized (mLock) {
+            return mA11yWindowManager.isTrackingWindowsLocked();
+        }
+    }
+
+    /**
+     * Gets the bounds of a window.
+     *
+     * @param outBounds The output to which to write the bounds.
+     */
+    boolean getWindowBounds(int windowId, Rect outBounds) {
+        IBinder token;
+        synchronized (mLock) {
+            token = getWindowToken(windowId, mCurrentUserId);
+        }
+        mWindowManagerService.getWindowFrame(token, outBounds);
+        if (!outBounds.isEmpty()) {
+            return true;
+        }
+        return false;
+    }
+
     public int getActiveWindowId() {
         return mA11yWindowManager.getActiveWindowId(mCurrentUserId);
     }
@@ -1824,9 +1868,11 @@
         for (int i = 0; !observingWindows && (i < boundServiceCount); i++) {
             AccessibilityServiceConnection boundService = boundServices.get(i);
             if (boundService.canRetrieveInteractiveWindowsLocked()) {
+                userState.setAccessibilityFocusOnlyInActiveWindow(false);
                 observingWindows = true;
             }
         }
+        userState.setAccessibilityFocusOnlyInActiveWindow(true);
 
         // Gets all valid displays and start tracking windows of each display if there is at least
         // one bound service that can retrieve window content.
@@ -2930,6 +2976,19 @@
         }
 
         /**
+         * Gets a point within the accessibility focused node where we can send down and up events
+         * to perform a click.
+         *
+         * @param outPoint The click point to populate.
+         * @return Whether accessibility a click point was found and set.
+         */
+        // TODO: (multi-display) Make sure this works for multiple displays.
+        boolean getAccessibilityFocusClickPointInScreen(Point outPoint) {
+            return getInteractionBridge()
+                    .getAccessibilityFocusClickPointInScreenNotLocked(outPoint);
+        }
+
+    /**
          * Perform an accessibility action on the view that currently has accessibility focus.
          * Has no effect if no item has accessibility focus, if the item with accessibility
          * focus does not expose the specified action, or if the action fails.
@@ -2947,6 +3006,43 @@
             return focus.performAction(action.getId());
         }
 
+        public boolean getAccessibilityFocusClickPointInScreenNotLocked(Point outPoint) {
+            AccessibilityNodeInfo focus = getAccessibilityFocusNotLocked();
+            if (focus == null) {
+                return false;
+            }
+
+            synchronized (mLock) {
+                Rect boundsInScreen = mTempRect;
+                focus.getBoundsInScreen(boundsInScreen);
+
+                // Apply magnification if needed.
+                MagnificationSpec spec = getCompatibleMagnificationSpecLocked(focus.getWindowId());
+                if (spec != null && !spec.isNop()) {
+                    boundsInScreen.offset((int) -spec.offsetX, (int) -spec.offsetY);
+                    boundsInScreen.scale(1 / spec.scale);
+                }
+
+                // Clip to the window bounds.
+                Rect windowBounds = mTempRect1;
+                getWindowBounds(focus.getWindowId(), windowBounds);
+                if (!boundsInScreen.intersect(windowBounds)) {
+                    return false;
+                }
+
+                // Clip to the screen bounds.
+                Point screenSize = mTempPoint;
+                mDefaultDisplay.getRealSize(screenSize);
+                if (!boundsInScreen.intersect(0, 0, screenSize.x, screenSize.y)) {
+                    return false;
+                }
+
+                outPoint.set(boundsInScreen.centerX(), boundsInScreen.centerY());
+            }
+
+            return true;
+        }
+
         private AccessibilityNodeInfo getAccessibilityFocusNotLocked() {
             final int focusedWindowId;
             synchronized (mLock) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 43bb4b3..0845d01 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -104,6 +104,7 @@
     private boolean mIsDisplayMagnificationEnabled;
     private boolean mIsFilterKeyEventsEnabled;
     private boolean mIsPerformGesturesEnabled;
+    private boolean mAccessibilityFocusOnlyInActiveWindow;
     private boolean mIsTextHighContrastEnabled;
     private boolean mIsTouchExplorationEnabled;
     private boolean mServiceHandlesDoubleTap;
@@ -685,6 +686,13 @@
         mIsPerformGesturesEnabled = enabled;
     }
 
+    public boolean isAccessibilityFocusOnlyInActiveWindow() {
+        return mAccessibilityFocusOnlyInActiveWindow;
+    }
+
+    public void setAccessibilityFocusOnlyInActiveWindow(boolean enabled) {
+        mAccessibilityFocusOnlyInActiveWindow = enabled;
+    }
     public ComponentName getServiceChangingSoftKeyboardModeLocked() {
         return mServiceChangingSoftKeyboardMode;
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/FullScreenMagnificationGestureHandler.java
index afe6238..b7f8e67 100644
--- a/services/accessibility/java/com/android/server/accessibility/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/FullScreenMagnificationGestureHandler.java
@@ -26,6 +26,7 @@
 
 import static com.android.internal.accessibility.util.AccessibilityStatsLogUtils.logMagnificationTripleTap;
 import static com.android.server.accessibility.gestures.GestureUtils.distance;
+import static com.android.server.accessibility.gestures.GestureUtils.distanceClosestPointerToPoint;
 
 import static java.lang.Math.abs;
 import static java.util.Arrays.asList;
@@ -37,6 +38,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.graphics.PointF;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -615,6 +617,7 @@
 
         private static final int MESSAGE_ON_TRIPLE_TAP_AND_HOLD = 1;
         private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2;
+        private static final int MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE = 3;
 
         final int mLongTapMinDelay;
         final int mSwipeMinDistance;
@@ -626,6 +629,7 @@
         private MotionEvent mPreLastDown;
         private MotionEvent mLastUp;
         private MotionEvent mPreLastUp;
+        private PointF mSecondPointerDownLocation = new PointF(Float.NaN, Float.NaN);
 
         private long mLastDetectingDownEventTime;
 
@@ -656,6 +660,10 @@
                     transitionToDelegatingStateAndClear();
                 }
                 break;
+                case MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE: {
+                    transitToPanningScalingStateAndClear();
+                }
+                break;
                 default: {
                     throw new IllegalArgumentException("Unknown message type: " + type);
                 }
@@ -702,14 +710,20 @@
                 }
                 break;
                 case ACTION_POINTER_DOWN: {
-                    if (mMagnificationController.isMagnifying(mDisplayId)) {
-                        transitionTo(mPanningScalingState);
-                        clear();
+                    if (mMagnificationController.isMagnifying(mDisplayId)
+                            && event.getPointerCount() == 2) {
+                        storeSecondPointerDownLocation(event);
+                        mHandler.sendEmptyMessageDelayed(MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE,
+                                ViewConfiguration.getTapTimeout());
                     } else {
                         transitionToDelegatingStateAndClear();
                     }
                 }
                 break;
+                case ACTION_POINTER_UP: {
+                    transitionToDelegatingStateAndClear();
+                }
+                break;
                 case ACTION_MOVE: {
                     if (isFingerDown()
                             && distance(mLastDown, /* move */ event) > mSwipeMinDistance) {
@@ -719,11 +733,19 @@
                         // For convenience, viewport dragging takes precedence
                         // over insta-delegating on 3tap&swipe
                         // (which is a rare combo to be used aside from magnification)
-                        if (isMultiTapTriggered(2 /* taps */)) {
+                        if (isMultiTapTriggered(2 /* taps */) && event.getPointerCount() == 1) {
                             transitionToViewportDraggingStateAndClear(event);
+                        } else if (isMagnifying() && event.getPointerCount() == 2) {
+                            //Primary pointer is swiping, so transit to PanningScalingState
+                            transitToPanningScalingStateAndClear();
                         } else {
                             transitionToDelegatingStateAndClear();
                         }
+                    } else if (isMagnifying() && secondPointerDownValid()
+                            && distanceClosestPointerToPoint(
+                            mSecondPointerDownLocation, /* move */ event) > mSwipeMinDistance) {
+                        //Second pointer is swiping, so transit to PanningScalingState
+                        transitToPanningScalingStateAndClear();
                     }
                 }
                 break;
@@ -755,6 +777,21 @@
             }
         }
 
+        private void storeSecondPointerDownLocation(MotionEvent event) {
+            final int index = event.getActionIndex();
+            mSecondPointerDownLocation.set(event.getX(index), event.getY(index));
+        }
+
+        private boolean secondPointerDownValid() {
+            return !(Float.isNaN(mSecondPointerDownLocation.x) && Float.isNaN(
+                    mSecondPointerDownLocation.y));
+        }
+
+        private void transitToPanningScalingStateAndClear() {
+            transitionTo(mPanningScalingState);
+            clear();
+        }
+
         public boolean isMultiTapTriggered(int numTaps) {
 
             // Shortcut acts as the 2 initial taps
@@ -822,11 +859,13 @@
             setShortcutTriggered(false);
             removePendingDelayedMessages();
             clearDelayedMotionEvents();
+            mSecondPointerDownLocation.set(Float.NaN, Float.NaN);
         }
 
         private void removePendingDelayedMessages() {
             mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
             mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
+            mHandler.removeMessages(MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE);
         }
 
         private void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent,
@@ -890,6 +929,7 @@
             transitionTo(mDelegatingState);
             sendDelayedMotionEvents();
             removePendingDelayedMessages();
+            mSecondPointerDownLocation.set(Float.NaN, Float.NaN);
         }
 
         private void onTripleTap(MotionEvent up) {
@@ -907,6 +947,10 @@
             }
         }
 
+        private boolean isMagnifying() {
+            return mMagnificationController.isMagnifying(mDisplayId);
+        }
+
         void transitionToViewportDraggingStateAndClear(MotionEvent down) {
 
             if (DEBUG_DETECTING) Slog.i(LOG_TAG, "onTripleTapAndHold()");
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java
index 667364c9..c8cee10 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java
@@ -21,8 +21,11 @@
 import static com.android.server.accessibility.gestures.TouchState.MAX_POINTER_COUNT;
 
 import android.content.Context;
+import android.graphics.Point;
 import android.util.Slog;
 import android.view.MotionEvent;
+import android.view.MotionEvent.PointerCoords;
+import android.view.MotionEvent.PointerProperties;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 
@@ -37,19 +40,27 @@
  */
 class EventDispatcher {
     private static final String LOG_TAG = "EventDispatcher";
+    private static final int CLICK_LOCATION_NONE = 0;
+    private static final int CLICK_LOCATION_ACCESSIBILITY_FOCUS = 1;
+    private static final int CLICK_LOCATION_LAST_TOUCH_EXPLORED = 2;
 
     private final AccessibilityManagerService mAms;
     private Context mContext;
     // The receiver of motion events.
     private EventStreamTransformation mReceiver;
-    // Keep track of which pointers sent to the system are down.
-    private int mInjectedPointersDown;
 
-    // The time of the last injected down.
-    private long mLastInjectedDownEventTime;
+    // The long pressing pointer id if coordinate remapping is needed for double tap and hold
+    private int mLongPressingPointerId = -1;
 
-    // The last injected hover event.
-    private MotionEvent mLastInjectedHoverEvent;
+    // The long pressing pointer X if coordinate remapping is needed for double tap and hold.
+    private int mLongPressingPointerDeltaX;
+
+    // The long pressing pointer Y if coordinate remapping is needed for double tap and hold.
+    private int mLongPressingPointerDeltaY;
+
+    // Temporary point to avoid instantiation.
+    private final Point mTempPoint = new Point();
+
     private TouchState mState;
 
     EventDispatcher(
@@ -98,8 +109,18 @@
         if (action == MotionEvent.ACTION_DOWN) {
             event.setDownTime(event.getEventTime());
         } else {
-            event.setDownTime(getLastInjectedDownEventTime());
+            event.setDownTime(mState.getLastInjectedDownEventTime());
         }
+        // If the user is long pressing but the long pressing pointer
+        // was not exactly over the accessibility focused item we need
+        // to remap the location of that pointer so the user does not
+        // have to explicitly touch explore something to be able to
+        // long press it, or even worse to avoid the user long pressing
+        // on the wrong item since click and long press behave differently.
+        if (mLongPressingPointerId >= 0) {
+            event = offsetEvent(event, -mLongPressingPointerDeltaX, -mLongPressingPointerDeltaY);
+        }
+
         if (DEBUG) {
             Slog.d(
                     LOG_TAG,
@@ -116,7 +137,7 @@
         } else {
             Slog.e(LOG_TAG, "Error sending event: no receiver specified.");
         }
-        updateState(event);
+        mState.onInjectedMotionEvent(event);
 
         if (event != prototype) {
             event.recycle();
@@ -145,87 +166,15 @@
         mState.onInjectedAccessibilityEvent(type);
     }
 
-    /**
-     * Processes an injected {@link MotionEvent} event.
-     *
-     * @param event The event to process.
-     */
-    void updateState(MotionEvent event) {
-        final int action = event.getActionMasked();
-        final int pointerId = event.getPointerId(event.getActionIndex());
-        final int pointerFlag = (1 << pointerId);
-        switch (action) {
-            case MotionEvent.ACTION_DOWN:
-            case MotionEvent.ACTION_POINTER_DOWN:
-                mInjectedPointersDown |= pointerFlag;
-                mLastInjectedDownEventTime = event.getDownTime();
-                break;
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_POINTER_UP:
-                mInjectedPointersDown &= ~pointerFlag;
-                if (mInjectedPointersDown == 0) {
-                    mLastInjectedDownEventTime = 0;
-                }
-                break;
-            case MotionEvent.ACTION_HOVER_ENTER:
-            case MotionEvent.ACTION_HOVER_MOVE:
-            case MotionEvent.ACTION_HOVER_EXIT:
-                if (mLastInjectedHoverEvent != null) {
-                    mLastInjectedHoverEvent.recycle();
-                }
-                mLastInjectedHoverEvent = MotionEvent.obtain(event);
-                break;
-        }
-        if (DEBUG) {
-            Slog.i(LOG_TAG, "Injected pointer:\n" + toString());
-        }
-    }
-
-    /** Clears the internals state. */
-    public void clear() {
-        mInjectedPointersDown = 0;
-    }
-
-    /** @return The time of the last injected down event. */
-    public long getLastInjectedDownEventTime() {
-        return mLastInjectedDownEventTime;
-    }
-
-    /** @return The number of down pointers injected to the view hierarchy. */
-    public int getInjectedPointerDownCount() {
-        return Integer.bitCount(mInjectedPointersDown);
-    }
-
-    /** @return The bits of the injected pointers that are down. */
-    public int getInjectedPointersDown() {
-        return mInjectedPointersDown;
-    }
-
-    /**
-     * Whether an injected pointer is down.
-     *
-     * @param pointerId The unique pointer id.
-     * @return True if the pointer is down.
-     */
-    public boolean isInjectedPointerDown(int pointerId) {
-        final int pointerFlag = (1 << pointerId);
-        return (mInjectedPointersDown & pointerFlag) != 0;
-    }
-
-    /** @return The the last injected hover event. */
-    public MotionEvent getLastInjectedHoverEvent() {
-        return mLastInjectedHoverEvent;
-    }
-
     @Override
     public String toString() {
         StringBuilder builder = new StringBuilder();
         builder.append("=========================");
         builder.append("\nDown pointers #");
-        builder.append(Integer.bitCount(mInjectedPointersDown));
+        builder.append(Integer.bitCount(mState.getInjectedPointersDown()));
         builder.append(" [ ");
         for (int i = 0; i < MAX_POINTER_COUNT; i++) {
-            if ((mInjectedPointersDown & i) != 0) {
+            if (mState.isInjectedPointerDown(i)) {
                 builder.append(i);
                 builder.append(" ");
             }
@@ -236,6 +185,48 @@
     }
 
     /**
+     * /** Offsets all pointers in the given event by adding the specified X and Y offsets.
+     *
+     * @param event The event to offset.
+     * @param offsetX The X offset.
+     * @param offsetY The Y offset.
+     * @return An event with the offset pointers or the original event if both offsets are zero.
+     */
+    private MotionEvent offsetEvent(MotionEvent event, int offsetX, int offsetY) {
+        if (offsetX == 0 && offsetY == 0) {
+            return event;
+        }
+        final int remappedIndex = event.findPointerIndex(mLongPressingPointerId);
+        final int pointerCount = event.getPointerCount();
+        PointerProperties[] props = PointerProperties.createArray(pointerCount);
+        PointerCoords[] coords = PointerCoords.createArray(pointerCount);
+        for (int i = 0; i < pointerCount; i++) {
+            event.getPointerProperties(i, props[i]);
+            event.getPointerCoords(i, coords[i]);
+            if (i == remappedIndex) {
+                coords[i].x += offsetX;
+                coords[i].y += offsetY;
+            }
+        }
+        return MotionEvent.obtain(
+                event.getDownTime(),
+                event.getEventTime(),
+                event.getAction(),
+                event.getPointerCount(),
+                props,
+                coords,
+                event.getMetaState(),
+                event.getButtonState(),
+                1.0f,
+                1.0f,
+                event.getDeviceId(),
+                event.getEdgeFlags(),
+                event.getSource(),
+                event.getDisplayId(),
+                event.getFlags());
+    }
+
+    /**
      * Computes the action for an injected event based on a masked action and a pointer index.
      *
      * @param actionMasked The masked action.
@@ -247,7 +238,7 @@
             case MotionEvent.ACTION_DOWN:
             case MotionEvent.ACTION_POINTER_DOWN:
                 // Compute the action based on how many down pointers are injected.
-                if (getInjectedPointerDownCount() == 0) {
+                if (mState.getInjectedPointerDownCount() == 0) {
                     return MotionEvent.ACTION_DOWN;
                 } else {
                     return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
@@ -255,7 +246,7 @@
                 }
             case MotionEvent.ACTION_POINTER_UP:
                 // Compute the action based on how many down pointers are injected.
-                if (getInjectedPointerDownCount() == 1) {
+                if (mState.getInjectedPointerDownCount() == 1) {
                     return MotionEvent.ACTION_UP;
                 } else {
                     return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
@@ -280,7 +271,7 @@
         for (int i = 0; i < pointerCount; i++) {
             final int pointerId = prototype.getPointerId(i);
             // Do not send event for already delivered pointers.
-            if (!isInjectedPointerDown(pointerId)) {
+            if (!mState.isInjectedPointerDown(pointerId)) {
                 pointerIdBits |= (1 << pointerId);
                 final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
                 sendMotionEvent(
@@ -306,7 +297,7 @@
         for (int i = 0; i < pointerCount; i++) {
             final int pointerId = prototype.getPointerId(i);
             // Skip non injected down pointers.
-            if (!isInjectedPointerDown(pointerId)) {
+            if (!mState.isInjectedPointerDown(pointerId)) {
                 continue;
             }
             final int action = computeInjectionAction(MotionEvent.ACTION_POINTER_UP, i);
@@ -315,4 +306,97 @@
             pointerIdBits &= ~(1 << pointerId);
         }
     }
+
+    public boolean longPressWithTouchEvents(MotionEvent event, int policyFlags) {
+        final int pointerIndex = event.getActionIndex();
+        final int pointerId = event.getPointerId(pointerIndex);
+        Point clickLocation = mTempPoint;
+        final int result = computeClickLocation(clickLocation);
+        if (result == CLICK_LOCATION_NONE) {
+            return false;
+        }
+        mLongPressingPointerId = pointerId;
+        mLongPressingPointerDeltaX = (int) event.getX(pointerIndex) - clickLocation.x;
+        mLongPressingPointerDeltaY = (int) event.getY(pointerIndex) - clickLocation.y;
+        sendDownForAllNotInjectedPointers(event, policyFlags);
+        return true;
+    }
+
+    public void clickWithTouchEvents(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        final int pointerIndex = event.getActionIndex();
+        final int pointerId = event.getPointerId(pointerIndex);
+        Point clickLocation = mTempPoint;
+        final int result = computeClickLocation(clickLocation);
+        if (result == CLICK_LOCATION_NONE) {
+            Slog.e(LOG_TAG, "Unable to compute click location.");
+            // We can't send a click to no location, but the gesture was still
+            // consumed.
+            return;
+        }
+        // Do the click.
+        PointerProperties[] properties = new PointerProperties[1];
+        properties[0] = new PointerProperties();
+        event.getPointerProperties(pointerIndex, properties[0]);
+        PointerCoords[] coords = new PointerCoords[1];
+        coords[0] = new PointerCoords();
+        coords[0].x = clickLocation.x;
+        coords[0].y = clickLocation.y;
+        MotionEvent clickEvent =
+                MotionEvent.obtain(
+                        event.getDownTime(),
+                        event.getEventTime(),
+                        MotionEvent.ACTION_DOWN,
+                        1,
+                        properties,
+                        coords,
+                        0,
+                        0,
+                        1.0f,
+                        1.0f,
+                        event.getDeviceId(),
+                        0,
+                        event.getSource(),
+                        event.getDisplayId(),
+                        event.getFlags());
+        final boolean targetAccessibilityFocus = (result == CLICK_LOCATION_ACCESSIBILITY_FOCUS);
+        sendActionDownAndUp(clickEvent, rawEvent, policyFlags, targetAccessibilityFocus);
+        clickEvent.recycle();
+    }
+
+    private int computeClickLocation(Point outLocation) {
+        if (mState.getLastInjectedHoverEventForClick() != null) {
+            final int lastExplorePointerIndex =
+                    mState.getLastInjectedHoverEventForClick().getActionIndex();
+            outLocation.x =
+                    (int) mState.getLastInjectedHoverEventForClick().getX(lastExplorePointerIndex);
+            outLocation.y =
+                    (int) mState.getLastInjectedHoverEventForClick().getY(lastExplorePointerIndex);
+            if (!mAms.accessibilityFocusOnlyInActiveWindow()
+                    || mState.getLastTouchedWindowId() == mAms.getActiveWindowId()) {
+                if (mAms.getAccessibilityFocusClickPointInScreen(outLocation)) {
+                    return CLICK_LOCATION_ACCESSIBILITY_FOCUS;
+                } else {
+                    return CLICK_LOCATION_LAST_TOUCH_EXPLORED;
+                }
+            }
+        }
+        if (mAms.getAccessibilityFocusClickPointInScreen(outLocation)) {
+            return CLICK_LOCATION_ACCESSIBILITY_FOCUS;
+        }
+        return CLICK_LOCATION_NONE;
+    }
+
+    private void sendActionDownAndUp(
+            MotionEvent prototype,
+            MotionEvent rawEvent,
+            int policyFlags,
+            boolean targetAccessibilityFocus) {
+        // Tap with the pointer that last explored.
+        final int pointerId = prototype.getPointerId(prototype.getActionIndex());
+        final int pointerIdBits = (1 << pointerId);
+        prototype.setTargetAccessibilityFocus(targetAccessibilityFocus);
+        sendMotionEvent(prototype, MotionEvent.ACTION_DOWN, rawEvent, pointerIdBits, policyFlags);
+        prototype.setTargetAccessibilityFocus(targetAccessibilityFocus);
+        sendMotionEvent(prototype, MotionEvent.ACTION_UP, rawEvent, pointerIdBits, policyFlags);
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
index 6d0f069..e9c70c6 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -104,6 +104,7 @@
         mHandler = new Handler(context.getMainLooper());
         mListener = listener;
         mState = state;
+        mMultiFingerGesturesEnabled = false;
         // Set up gestures.
         // Start with double tap.
         mGestures.add(new MultiTap(context, 2, GESTURE_DOUBLE_TAP, this));
@@ -247,7 +248,7 @@
          * and hold is dispatched via onGestureCompleted. Otherwise, this method is called when the
          * user has performed a double tap and then held down the second tap.
          */
-        void onDoubleTapAndHold();
+        void onDoubleTapAndHold(MotionEvent event, MotionEvent rawEvent, int policyFlags);
 
         /**
          * When FLAG_SERVICE_HANDLES_DOUBLE_TAP is enabled, this method is not called; double-tap is
@@ -256,7 +257,7 @@
          *
          * @return true if the event is consumed, else false
          */
-        boolean onDoubleTap();
+        boolean onDoubleTap(MotionEvent event, MotionEvent rawEvent, int policyFlags);
 
         /**
          * Called when the system has decided the event stream is a potential gesture.
@@ -322,7 +323,7 @@
                             new AccessibilityGestureEvent(gestureId, event.getDisplayId());
                     mListener.onGestureCompleted(gestureEvent);
                 } else {
-                    mListener.onDoubleTap();
+                    mListener.onDoubleTap(event, rawEvent, policyFlags);
                 }
                 clear();
                 break;
@@ -332,7 +333,7 @@
                             new AccessibilityGestureEvent(gestureId, event.getDisplayId());
                     mListener.onGestureCompleted(gestureEvent);
                 } else {
-                    mListener.onDoubleTapAndHold();
+                    mListener.onDoubleTapAndHold(event, rawEvent, policyFlags);
                 }
                 clear();
                 break;
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureUtils.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureUtils.java
index ac67480..ec30418 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureUtils.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureUtils.java
@@ -1,5 +1,6 @@
 package com.android.server.accessibility.gestures;
 
+import android.graphics.PointF;
 import android.util.MathUtils;
 import android.view.MotionEvent;
 
@@ -38,6 +39,27 @@
         return MathUtils.dist(first.getX(), first.getY(), second.getX(), second.getY());
     }
 
+    /**
+     * Returns the minimum distance between {@code pointerDown} and each pointer of
+     * {@link MotionEvent}.
+     *
+     * @param pointerDown The action pointer location of the {@link MotionEvent} with
+     *     {@link MotionEvent#ACTION_DOWN} or {@link MotionEvent#ACTION_POINTER_DOWN}
+     * @param moveEvent The {@link MotionEvent} with {@link MotionEvent#ACTION_MOVE}
+     * @return the movement of the pointer.
+     */
+    public static double distanceClosestPointerToPoint(PointF pointerDown, MotionEvent moveEvent) {
+        float movement = Float.MAX_VALUE;
+        for (int i = 0; i < moveEvent.getPointerCount(); i++) {
+            final float moveDelta = MathUtils.dist(pointerDown.x, pointerDown.y, moveEvent.getX(i),
+                    moveEvent.getY(i));
+            if (movement > moveDelta) {
+                movement = moveDelta;
+            }
+        }
+        return movement;
+    }
+
     public static boolean isTimedOut(MotionEvent firstUp, MotionEvent secondUp, int timeout) {
         final long deltaTime = secondUp.getEventTime() - firstUp.getEventTime();
         return (deltaTime >= timeout);
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index 4fee672..373d47e 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -22,7 +22,6 @@
 
 import android.accessibilityservice.AccessibilityGestureEvent;
 import android.content.Context;
-import android.graphics.Point;
 import android.graphics.Region;
 import android.os.Handler;
 import android.util.Slog;
@@ -86,6 +85,7 @@
     // The ID of the pointer used for dragging.
     private int mDraggingPointerId;
 
+
     // Handler for performing asynchronous operations.
     private final Handler mHandler;
 
@@ -115,8 +115,6 @@
     // Handle to the accessibility manager service.
     private final AccessibilityManagerService mAms;
 
-    // Temporary point to avoid instantiation.
-    private final Point mTempPoint = new Point();
 
     // Context in which this explorer operates.
     private final Context mContext;
@@ -277,6 +275,7 @@
         if (eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
             sendsPendingA11yEventsIfNeed();
         }
+        mState.onReceivedAccessibilityEvent(event);
         super.onAccessibilityEvent(event);
     }
 
@@ -309,16 +308,20 @@
     }
 
     @Override
-    public void onDoubleTapAndHold() {
+    public void onDoubleTapAndHold(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         // Try to use the standard accessibility API to long click
         if (!mAms.performActionOnAccessibilityFocusedItem(
                 AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK)) {
             Slog.e(LOG_TAG, "ACTION_LONG_CLICK failed.");
+            if (mDispatcher.longPressWithTouchEvents(event, policyFlags)) {
+                sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
+                mState.startDelegating();
+            }
         }
     }
 
     @Override
-    public boolean onDoubleTap() {
+    public boolean onDoubleTap(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         mAms.onTouchInteractionEnd();
         // Remove pending event deliveries.
         mSendHoverEnterAndMoveDelayed.cancel();
@@ -334,7 +337,10 @@
         // Try to use the standard accessibility API to click
         if (!mAms.performActionOnAccessibilityFocusedItem(
                 AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK)) {
-            Slog.e(LOG_TAG, "ACTION_CLICK failed.");
+            Slog.e(LOG_TAG, "ACTION_CLICK failed. Dispatching motion events to simulate click.");
+
+            mDispatcher.clickWithTouchEvents(event, rawEvent, policyFlags);
+            return true;
         }
         return true;
     }
@@ -840,7 +846,7 @@
      * @param policyFlags The policy flags associated with the event.
      */
     private void sendHoverExitAndTouchExplorationGestureEndIfNeeded(int policyFlags) {
-        MotionEvent event = mDispatcher.getLastInjectedHoverEvent();
+        MotionEvent event = mState.getLastInjectedHoverEvent();
         if (event != null && event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) {
             final int pointerIdBits = event.getPointerIdBits();
             if (!mSendTouchExplorationEndDelayed.isPending()) {
@@ -862,7 +868,7 @@
      * @param policyFlags The policy flags associated with the event.
      */
     private void sendTouchExplorationGestureStartAndHoverEnterIfNeeded(int policyFlags) {
-        MotionEvent event = mDispatcher.getLastInjectedHoverEvent();
+        MotionEvent event = mState.getLastInjectedHoverEvent();
         if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
             final int pointerIdBits = event.getPointerIdBits();
             mDispatcher.sendMotionEvent(
@@ -1188,7 +1194,6 @@
                 + ", mDetermineUserIntentTimeout: " + mDetermineUserIntentTimeout
                 + ", mDoubleTapSlop: " + mDoubleTapSlop
                 + ", mDraggingPointerId: " + mDraggingPointerId
-                + ", mTempPoint: " + mTempPoint
                 + " }";
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
index d23dbbe..7a39bc2 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
@@ -75,6 +75,16 @@
     private MotionEvent mLastReceivedEvent;
     // The accompanying raw event without any transformations.
     private MotionEvent mLastReceivedRawEvent;
+    // The id of the last touch explored window.
+    private int mLastTouchedWindowId;
+    // The last injected hover event.
+    private MotionEvent mLastInjectedHoverEvent;
+    // The last injected hover event used for performing clicks.
+    private MotionEvent mLastInjectedHoverEventForClick;
+    // The time of the last injected down.
+    private long mLastInjectedDownEventTime;
+    // Keep track of which pointers sent to the system are down.
+    private int mInjectedPointersDown;
 
     public TouchState() {
         mReceivedPointerTracker = new ReceivedPointerTracker();
@@ -88,7 +98,9 @@
             mLastReceivedEvent.recycle();
             mLastReceivedEvent = null;
         }
+        mLastTouchedWindowId = -1;
         mReceivedPointerTracker.clear();
+        mInjectedPointersDown = 0;
     }
 
     /**
@@ -107,6 +119,71 @@
         mReceivedPointerTracker.onMotionEvent(rawEvent);
     }
 
+    /**
+     * Processes an injected {@link MotionEvent} event.
+     *
+     * @param event The event to process.
+     */
+    void onInjectedMotionEvent(MotionEvent event) {
+        final int action = event.getActionMasked();
+        final int pointerId = event.getPointerId(event.getActionIndex());
+        final int pointerFlag = (1 << pointerId);
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_POINTER_DOWN:
+                mInjectedPointersDown |= pointerFlag;
+                mLastInjectedDownEventTime = event.getDownTime();
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_POINTER_UP:
+                mInjectedPointersDown &= ~pointerFlag;
+                if (mInjectedPointersDown == 0) {
+                    mLastInjectedDownEventTime = 0;
+                }
+                break;
+            case MotionEvent.ACTION_HOVER_ENTER:
+            case MotionEvent.ACTION_HOVER_MOVE:
+                if (mLastInjectedHoverEvent != null) {
+                    mLastInjectedHoverEvent.recycle();
+                }
+                mLastInjectedHoverEvent = MotionEvent.obtain(event);
+                break;
+            case MotionEvent.ACTION_HOVER_EXIT:
+                if (mLastInjectedHoverEvent != null) {
+                    mLastInjectedHoverEvent.recycle();
+                }
+                mLastInjectedHoverEvent = MotionEvent.obtain(event);
+                if (mLastInjectedHoverEventForClick != null) {
+                    mLastInjectedHoverEventForClick.recycle();
+                }
+                mLastInjectedHoverEventForClick = MotionEvent.obtain(event);
+                break;
+        }
+        if (DEBUG) {
+            Slog.i(LOG_TAG, "Injected pointer:\n" + toString());
+        }
+    }
+
+    /** Updates state in response to an accessibility event received from the outside. */
+    public void onReceivedAccessibilityEvent(AccessibilityEvent event) {
+        // If a new window opens or the accessibility focus moves we no longer
+        // want to click/long press on the last touch explored location.
+        switch (event.getEventType()) {
+            case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
+            case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED:
+                if (mLastInjectedHoverEventForClick != null) {
+                    mLastInjectedHoverEventForClick.recycle();
+                    mLastInjectedHoverEventForClick = null;
+                }
+                mLastTouchedWindowId = -1;
+                break;
+            case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
+            case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT:
+                mLastTouchedWindowId = event.getWindowId();
+                break;
+        }
+    }
+
     public void onInjectedAccessibilityEvent(int type) {
         // The below state transitions go here because the related events are often sent on a
         // delay.
@@ -236,6 +313,46 @@
         return mLastReceivedEvent;
     }
 
+    /** @return The the last injected hover event. */
+    public MotionEvent getLastInjectedHoverEvent() {
+        return mLastInjectedHoverEvent;
+    }
+
+    /** @return The time of the last injected down event. */
+    public long getLastInjectedDownEventTime() {
+        return mLastInjectedDownEventTime;
+    }
+
+    public int getLastTouchedWindowId() {
+        return mLastTouchedWindowId;
+    }
+
+    /** @return The number of down pointers injected to the view hierarchy. */
+    public int getInjectedPointerDownCount() {
+        return Integer.bitCount(mInjectedPointersDown);
+    }
+
+    /** @return The bits of the injected pointers that are down. */
+    public int getInjectedPointersDown() {
+        return mInjectedPointersDown;
+    }
+
+    /**
+     * Whether an injected pointer is down.
+     *
+     * @param pointerId The unique pointer id.
+     * @return True if the pointer is down.
+     */
+    public boolean isInjectedPointerDown(int pointerId) {
+        final int pointerFlag = (1 << pointerId);
+        return (mInjectedPointersDown & pointerFlag) != 0;
+    }
+
+    /** @return The the last injected hover event used for a click. */
+    public MotionEvent getLastInjectedHoverEventForClick() {
+        return mLastInjectedHoverEventForClick;
+    }
+
     /** This class tracks where and when a pointer went down. It does not track its movement. */
     class ReceivedPointerTracker {
         private static final String LOG_TAG_RECEIVED_POINTER_TRACKER = "ReceivedPointerTracker";
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 11f9015..533bbe6 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -149,7 +149,7 @@
             @Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
             @Nullable Function<InlineFillUi, Boolean> inlineSuggestionsCallback,
             @NonNull Runnable onErrorCallback,
-            @Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
+            @Nullable RemoteInlineSuggestionRenderService remoteRenderService, int userId) {
         long requestTime = SystemClock.elapsedRealtime();
         AtomicReference<ICancellationSignal> cancellationRef = new AtomicReference<>();
 
@@ -173,7 +173,7 @@
                                             inlineSuggestionsRequest, inlineSuggestionsData,
                                             clientState, focusedId, focusedValue,
                                             inlineSuggestionsCallback,
-                                            client, onErrorCallback, remoteRenderService);
+                                            client, onErrorCallback, remoteRenderService, userId);
                                     if (!showingFillWindow) {
                                         requestAutofill.complete(null);
                                     }
@@ -243,7 +243,8 @@
             @NonNull AutofillId focusedId, @Nullable AutofillValue focusedValue,
             @Nullable Function<InlineFillUi, Boolean> inlineSuggestionsCallback,
             @NonNull IAutoFillManagerClient client, @NonNull Runnable onErrorCallback,
-            @Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
+            @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
+            int userId) {
         if (inlineSuggestionsData == null || inlineSuggestionsData.isEmpty()
                 || inlineSuggestionsCallback == null || request == null
                 || remoteRenderService == null) {
@@ -312,7 +313,7 @@
                                     Slog.w(TAG, "RemoteException starting intent sender");
                                 }
                             }
-                        }, onErrorCallback, remoteRenderService);
+                        }, onErrorCallback, remoteRenderService, userId, sessionId);
 
         if (inlineSuggestionsCallback.apply(inlineFillUi)) {
             mCallbacks.logAugmentedAutofillShown(sessionId, clientState);
diff --git a/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java b/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java
index 617c111..80b8583 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java
@@ -88,9 +88,9 @@
      */
     public void renderSuggestion(@NonNull IInlineSuggestionUiCallback callback,
             @NonNull InlinePresentation presentation, int width, int height,
-            @Nullable IBinder hostInputToken, int displayId) {
+            @Nullable IBinder hostInputToken, int displayId, int userId, int sessionId) {
         scheduleAsyncRequest((s) -> s.renderSuggestion(callback, presentation, width, height,
-                hostInputToken, displayId));
+                hostInputToken, displayId, userId, sessionId));
     }
 
     /**
@@ -100,6 +100,13 @@
         scheduleAsyncRequest((s) -> s.getInlineSuggestionsRendererInfo(callback));
     }
 
+    /**
+     * Destroys the remote inline suggestion views associated with the given user id and session id.
+     */
+    public void destroySuggestionViews(int userId, int sessionId) {
+        scheduleAsyncRequest((s) -> s.destroySuggestionViews(userId, sessionId));
+    }
+
     @Nullable
     private static ServiceInfo getServiceInfo(Context context, int userId) {
         final String packageName =
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 2b9ce2f..9b3d075 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -155,6 +155,9 @@
      */
     public final int id;
 
+    /** userId the session belongs to */
+    public final int userId;
+
     /** uid the session is for */
     public final int uid;
 
@@ -823,6 +826,7 @@
         }
         id = sessionId;
         mFlags = flags;
+        this.userId = userId;
         this.taskId = taskId;
         this.uid = uid;
         mStartTime = SystemClock.elapsedRealtime();
@@ -2986,7 +2990,7 @@
                         mInlineSessionController.setInlineFillUiLocked(
                                 InlineFillUi.emptyUi(focusedId));
                     }
-                }, remoteRenderService);
+                }, remoteRenderService, userId, id);
         return mInlineSessionController.setInlineFillUiLocked(inlineFillUi);
     }
 
@@ -3296,7 +3300,7 @@
                                         mInlineSessionController.setInlineFillUiLocked(
                                                 InlineFillUi.emptyUi(mCurrentViewId));
                                     }
-                                }, mService.getRemoteInlineSuggestionRenderServiceLocked());
+                                }, mService.getRemoteInlineSuggestionRenderServiceLocked(), userId);
                     }
                 };
 
@@ -3796,6 +3800,12 @@
         if (mCurrentViewId != null) {
             mInlineSessionController.destroyLocked(mCurrentViewId);
         }
+        final RemoteInlineSuggestionRenderService remoteRenderService =
+                mService.getRemoteInlineSuggestionRenderServiceLocked();
+        if (remoteRenderService != null) {
+            remoteRenderService.destroySuggestionViews(userId, id);
+        }
+
         mDestroyed = true;
 
         // Log metrics
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java
index 627c073..25e9d5c 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java
@@ -105,19 +105,20 @@
             @NonNull AutofillId focusedViewId, @Nullable String filterText,
             @NonNull AutoFillUI.AutoFillUiCallback uiCallback,
             @NonNull Runnable onErrorCallback,
-            @Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
+            @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
+            int userId, int sessionId) {
 
         if (InlineSuggestionFactory.responseNeedAuthentication(response)) {
             InlineSuggestion inlineAuthentication =
                     InlineSuggestionFactory.createInlineAuthentication(request, response,
-                            focusedViewId, uiCallback, onErrorCallback, remoteRenderService);
+                            uiCallback, onErrorCallback, remoteRenderService, userId, sessionId);
             return new InlineFillUi(focusedViewId, inlineAuthentication, filterText);
         } else if (response.getDatasets() != null) {
             SparseArray<Pair<Dataset, InlineSuggestion>> inlineSuggestions =
                     InlineSuggestionFactory.createAutofillInlineSuggestions(request,
                             response.getRequestId(),
                             response.getDatasets(), focusedViewId, uiCallback, onErrorCallback,
-                            remoteRenderService);
+                            remoteRenderService, userId, sessionId);
             return new InlineFillUi(focusedViewId, inlineSuggestions, filterText);
         }
         return new InlineFillUi(focusedViewId, new SparseArray<>(), filterText);
@@ -132,11 +133,12 @@
             @NonNull AutofillId focusedViewId, @Nullable String filterText,
             @NonNull InlineSuggestionUiCallback uiCallback,
             @NonNull Runnable onErrorCallback,
-            @Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
+            @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
+            int userId, int sessionId) {
         SparseArray<Pair<Dataset, InlineSuggestion>> inlineSuggestions =
                 InlineSuggestionFactory.createAugmentedAutofillInlineSuggestions(request, datasets,
                         focusedViewId,
-                        uiCallback, onErrorCallback, remoteRenderService);
+                        uiCallback, onErrorCallback, remoteRenderService, userId, sessionId);
         return new InlineFillUi(focusedViewId, inlineSuggestions, filterText);
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
index 462ffd6..8fcb8aa 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
@@ -53,9 +53,9 @@
 
     public static InlineSuggestion createInlineAuthentication(
             @NonNull InlineSuggestionsRequest request, @NonNull FillResponse response,
-            @NonNull AutofillId autofillId,
             @NonNull AutoFillUI.AutoFillUiCallback client, @NonNull Runnable onErrorCallback,
-            @Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
+            @Nullable RemoteInlineSuggestionRenderService remoteRenderService, int userId,
+            int sessionId) {
         final BiConsumer<Dataset, Integer> onClickFactory = (dataset, datasetIndex) -> {
             client.authenticate(response.getRequestId(),
                     datasetIndex, response.getAuthentication(), response.getClientState(),
@@ -66,7 +66,8 @@
         InlinePresentation inlineAuthentication = response.getInlinePresentation();
         return createInlineAuthSuggestion(
                 mergedInlinePresentation(request, 0, inlineAuthentication),
-                remoteRenderService, onClickFactory, onErrorCallback, intentSenderConsumer,
+                remoteRenderService, userId, sessionId,
+                onClickFactory, onErrorCallback, intentSenderConsumer,
                 request.getHostInputToken(), request.getHostDisplayId());
     }
 
@@ -80,7 +81,8 @@
             @NonNull List<Dataset> datasets,
             @NonNull AutofillId autofillId,
             @NonNull AutoFillUI.AutoFillUiCallback client, @NonNull Runnable onErrorCallback,
-            @Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
+            @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
+            int userId, int sessionId) {
         if (sDebug) Slog.d(TAG, "createInlineSuggestionsResponse called");
         final Consumer<IntentSender> intentSenderConsumer = (intentSender) ->
                 client.startIntentSender(intentSender, new Intent());
@@ -90,7 +92,8 @@
 
         return createInlineSuggestionsInternal(/* isAugmented= */ false, request,
                 datasets, autofillId,
-                onErrorCallback, onClickFactory, intentSenderConsumer, remoteRenderService);
+                onErrorCallback, onClickFactory, intentSenderConsumer, remoteRenderService, userId,
+                sessionId);
     }
 
     /**
@@ -104,7 +107,8 @@
             @NonNull AutofillId autofillId,
             @NonNull InlineFillUi.InlineSuggestionUiCallback inlineSuggestionUiCallback,
             @NonNull Runnable onErrorCallback,
-            @Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
+            @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
+            int userId, int sessionId) {
         if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called");
         return createInlineSuggestionsInternal(/* isAugmented= */ true, request,
                 datasets, autofillId, onErrorCallback,
@@ -112,7 +116,7 @@
                         inlineSuggestionUiCallback.autofill(dataset, datasetIndex),
                 (intentSender) ->
                         inlineSuggestionUiCallback.startIntentSender(intentSender, new Intent()),
-                remoteRenderService);
+                remoteRenderService, userId, sessionId);
     }
 
     @Nullable
@@ -121,7 +125,8 @@
             @NonNull List<Dataset> datasets, @NonNull AutofillId autofillId,
             @NonNull Runnable onErrorCallback, @NonNull BiConsumer<Dataset, Integer> onClickFactory,
             @NonNull Consumer<IntentSender> intentSenderConsumer,
-            @Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
+            @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
+            int userId, int sessionId) {
         SparseArray<Pair<Dataset, InlineSuggestion>> response = new SparseArray<>(datasets.size());
         for (int datasetIndex = 0; datasetIndex < datasets.size(); datasetIndex++) {
             final Dataset dataset = datasets.get(datasetIndex);
@@ -139,7 +144,8 @@
             InlineSuggestion inlineSuggestion = createInlineSuggestion(isAugmented, dataset,
                     datasetIndex,
                     mergedInlinePresentation(request, datasetIndex, inlinePresentation),
-                    onClickFactory, remoteRenderService, onErrorCallback, intentSenderConsumer,
+                    onClickFactory, remoteRenderService, userId, sessionId,
+                    onErrorCallback, intentSenderConsumer,
                     request.getHostInputToken(), request.getHostDisplayId());
             response.append(datasetIndex, Pair.create(dataset, inlineSuggestion));
         }
@@ -151,6 +157,7 @@
             @NonNull InlinePresentation inlinePresentation,
             @NonNull BiConsumer<Dataset, Integer> onClickFactory,
             @NonNull RemoteInlineSuggestionRenderService remoteRenderService,
+            int userId, int sessionId,
             @NonNull Runnable onErrorCallback, @NonNull Consumer<IntentSender> intentSenderConsumer,
             @Nullable IBinder hostInputToken,
             int displayId) {
@@ -167,7 +174,8 @@
         final InlineSuggestion inlineSuggestion = new InlineSuggestion(inlineSuggestionInfo,
                 createInlineContentProvider(inlinePresentation,
                         () -> onClickFactory.accept(dataset, datasetIndex), onErrorCallback,
-                        intentSenderConsumer, remoteRenderService, hostInputToken, displayId));
+                        intentSenderConsumer, remoteRenderService, userId, sessionId,
+                        hostInputToken, displayId));
 
         return inlineSuggestion;
     }
@@ -175,6 +183,7 @@
     private static InlineSuggestion createInlineAuthSuggestion(
             @NonNull InlinePresentation inlinePresentation,
             @NonNull RemoteInlineSuggestionRenderService remoteRenderService,
+            int userId, int sessionId,
             @NonNull BiConsumer<Dataset, Integer> onClickFactory, @NonNull Runnable onErrorCallback,
             @NonNull Consumer<IntentSender> intentSenderConsumer,
             @Nullable IBinder hostInputToken, int displayId) {
@@ -187,8 +196,8 @@
                 createInlineContentProvider(inlinePresentation,
                         () -> onClickFactory.accept(null,
                                 AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED),
-                        onErrorCallback, intentSenderConsumer, remoteRenderService, hostInputToken,
-                        displayId));
+                        onErrorCallback, intentSenderConsumer, remoteRenderService, userId,
+                        sessionId, hostInputToken, displayId));
     }
 
     /**
@@ -216,12 +225,13 @@
             @NonNull Runnable onErrorCallback,
             @NonNull Consumer<IntentSender> intentSenderConsumer,
             @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
+            int userId, int sessionId,
             @Nullable IBinder hostInputToken,
             int displayId) {
         RemoteInlineSuggestionViewConnector
                 remoteInlineSuggestionViewConnector = new RemoteInlineSuggestionViewConnector(
-                remoteRenderService, inlinePresentation, hostInputToken, displayId, onClickAction,
-                onErrorCallback, intentSenderConsumer);
+                remoteRenderService, userId, sessionId, inlinePresentation, hostInputToken,
+                displayId, onClickAction, onErrorCallback, intentSenderConsumer);
         InlineContentProviderImpl inlineContentProvider = new InlineContentProviderImpl(
                 remoteInlineSuggestionViewConnector, null);
         return inlineContentProvider;
diff --git a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
index 9d23c17..7257255 100644
--- a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
+++ b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
@@ -46,6 +46,8 @@
     @Nullable
     private final IBinder mHostInputToken;
     private final int mDisplayId;
+    private final int mUserId;
+    private final int mSessionId;
 
     @NonNull
     private final Runnable mOnAutofillCallback;
@@ -56,6 +58,7 @@
 
     RemoteInlineSuggestionViewConnector(
             @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
+            int userId, int sessionId,
             @NonNull InlinePresentation inlinePresentation,
             @Nullable IBinder hostInputToken,
             int displayId,
@@ -66,6 +69,8 @@
         mInlinePresentation = inlinePresentation;
         mHostInputToken = hostInputToken;
         mDisplayId = displayId;
+        mUserId = userId;
+        mSessionId = sessionId;
 
         mOnAutofillCallback = onAutofillCallback;
         mOnErrorCallback = onErrorCallback;
@@ -82,7 +87,7 @@
         if (mRemoteRenderService != null) {
             if (sDebug) Slog.d(TAG, "Request to recreate the UI");
             mRemoteRenderService.renderSuggestion(callback, mInlinePresentation, width, height,
-                    mHostInputToken, mDisplayId);
+                    mHostInputToken, mDisplayId, mUserId, mSessionId);
             return true;
         }
         return false;
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 65e98ac..4bba0d8 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -95,7 +95,6 @@
         "android.hardware.light-V2.0-java",
         "android.hardware.power-java",
         "android.hardware.power-V1.0-java",
-        "android.hardware.tv.cec-V1.0-java",
         "android.hardware.vibrator-java",
         "android.net.ipsec.ike.stubs.module_lib",
         "app-compat-annotations",
@@ -117,6 +116,7 @@
         "android.hardware.health-V2.0-java",
         "android.hardware.health-V2.1-java",
         "android.hardware.light-java",
+        "android.hardware.tv.cec-V1.0-java",
         "android.hardware.weaver-V1.0-java",
         "android.hardware.biometrics.face-V1.0-java",
         "android.hardware.biometrics.fingerprint-V2.2-java",
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 4ff421e..135ac9a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -87,6 +87,7 @@
     static final String KEY_PROCESS_START_ASYNC = "process_start_async";
     static final String KEY_MEMORY_INFO_THROTTLE_TIME = "memory_info_throttle_time";
     static final String KEY_TOP_TO_FGS_GRACE_DURATION = "top_to_fgs_grace_duration";
+    static final String KEY_PENDINGINTENT_WARNING_THRESHOLD = "pendingintent_warning_threshold";
 
     private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
     private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
@@ -119,6 +120,7 @@
     private static final boolean DEFAULT_PROCESS_START_ASYNC = true;
     private static final long DEFAULT_MEMORY_INFO_THROTTLE_TIME = 5*60*1000;
     private static final long DEFAULT_TOP_TO_FGS_GRACE_DURATION = 15 * 1000;
+    private static final int DEFAULT_PENDINGINTENT_WARNING_THRESHOLD = 2000;
 
     // Flag stored in the DeviceConfig API.
     /**
@@ -328,6 +330,12 @@
      */
     public ArraySet<Integer> IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES = new ArraySet<Integer>();
 
+    /**
+     * The threshold for the amount of PendingIntent for each UID, there will be
+     * warning logs if the number goes beyond this threshold.
+     */
+    public int PENDINGINTENT_WARNING_THRESHOLD =  DEFAULT_PENDINGINTENT_WARNING_THRESHOLD;
+
     private List<String> mDefaultImperceptibleKillExemptPackages;
     private List<Integer> mDefaultImperceptibleKillExemptProcStates;
 
@@ -562,6 +570,8 @@
                     DEFAULT_MEMORY_INFO_THROTTLE_TIME);
             TOP_TO_FGS_GRACE_DURATION = mParser.getDurationMillis(KEY_TOP_TO_FGS_GRACE_DURATION,
                     DEFAULT_TOP_TO_FGS_GRACE_DURATION);
+            PENDINGINTENT_WARNING_THRESHOLD = mParser.getInt(KEY_PENDINGINTENT_WARNING_THRESHOLD,
+                    DEFAULT_PENDINGINTENT_WARNING_THRESHOLD);
 
             // For new flags that are intended for server-side experiments, please use the new
             // DeviceConfig package.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 157feb3..a5d99e0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -82,8 +82,6 @@
 import static android.os.Process.sendSignal;
 import static android.os.Process.setThreadPriority;
 import static android.os.Process.setThreadScheduler;
-import static android.permission.PermissionManager.KILL_APP_REASON_GIDS_CHANGED;
-import static android.permission.PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED;
 import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
 import static android.provider.Settings.Global.DEBUG_APP;
 import static android.provider.Settings.Global.NETWORK_ACCESS_TIMEOUT_MS;
@@ -2543,7 +2541,8 @@
         mUiHandler = injector.getUiHandler(null /* service */);
         mUserController = hasHandlerThread ? new UserController(this) : null;
         mPendingIntentController = hasHandlerThread
-                ? new PendingIntentController(handlerThread.getLooper(), mUserController) : null;
+                ? new PendingIntentController(handlerThread.getLooper(), mUserController,
+                        mConstants) : null;
         mProcStartHandlerThread = null;
         mProcStartHandler = null;
         mHiddenApiBlacklist = null;
@@ -2640,7 +2639,7 @@
         mUserController = new UserController(this);
 
         mPendingIntentController = new PendingIntentController(
-                mHandlerThread.getLooper(), mUserController);
+                mHandlerThread.getLooper(), mUserController, mConstants);
 
         if (SystemProperties.getInt("sys.use_fifo_ui", 0) != 0) {
             mUseFifoUiScheduling = true;
@@ -9201,16 +9200,31 @@
         synchronized (this) {
             final long identity = Binder.clearCallingIdentity();
             try {
-                boolean permissionChange = KILL_APP_REASON_PERMISSIONS_REVOKED.equals(reason)
-                        || KILL_APP_REASON_GIDS_CHANGED.equals(reason);
                 mProcessList.killPackageProcessesLocked(null /* packageName */, appId, userId,
                         ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */,
                         true /* callerWillRestart */, true /* doit */, true /* evenPersistent */,
                         false /* setRemoved */,
-                        permissionChange ? ApplicationExitInfo.REASON_PERMISSION_CHANGE
-                        : ApplicationExitInfo.REASON_OTHER,
-                        permissionChange ? ApplicationExitInfo.SUBREASON_UNKNOWN
-                        : ApplicationExitInfo.SUBREASON_KILL_UID,
+                        ApplicationExitInfo.REASON_OTHER,
+                        ApplicationExitInfo.SUBREASON_KILL_UID,
+                        reason != null ? reason : "kill uid");
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    @Override
+    public void killUidForPermissionChange(int appId, int userId, String reason) {
+        enforceCallingPermission(Manifest.permission.KILL_UID, "killUid");
+        synchronized (this) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mProcessList.killPackageProcessesLocked(null /* packageName */, appId, userId,
+                        ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */,
+                        true /* callerWillRestart */, true /* doit */, true /* evenPersistent */,
+                        false /* setRemoved */,
+                        ApplicationExitInfo.REASON_PERMISSION_CHANGE,
+                        ApplicationExitInfo.SUBREASON_UNKNOWN,
                         reason != null ? reason : "kill uid");
             } finally {
                 Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java
index eacf088..c62df56 100644
--- a/services/core/java/com/android/server/am/PendingIntentController.java
+++ b/services/core/java/com/android/server/am/PendingIntentController.java
@@ -41,8 +41,12 @@
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.IResultReceiver;
+import com.android.internal.util.RingBuffer;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.AlarmManagerInternal;
 import com.android.server.LocalServices;
@@ -52,6 +56,7 @@
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
 
@@ -66,6 +71,9 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "PendingIntentController" : TAG_AM;
     private static final String TAG_MU = TAG + POSTFIX_MU;
 
+    /** @see {@link #mRecentIntentsPerUid}.  */
+    private static final int RECENT_N = 10;
+
     /** Lock for internal state. */
     final Object mLock = new Object();
     final Handler mH;
@@ -77,10 +85,22 @@
     final HashMap<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>> mIntentSenderRecords
             = new HashMap<>();
 
-    PendingIntentController(Looper looper, UserController userController) {
+    /** The number of PendingIntentRecord per uid */
+    @GuardedBy("mLock")
+    private final SparseIntArray mIntentsPerUid = new SparseIntArray();
+
+    /** The recent PendingIntentRecord, up to {@link #RECENT_N} per uid */
+    @GuardedBy("mLock")
+    private final SparseArray<RingBuffer<String>> mRecentIntentsPerUid = new SparseArray<>();
+
+    private final ActivityManagerConstants mConstants;
+
+    PendingIntentController(Looper looper, UserController userController,
+            ActivityManagerConstants constants) {
         mH = new Handler(looper);
         mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
         mUserController = userController;
+        mConstants = constants;
     }
 
     void onActivityManagerInternalAdded() {
@@ -136,12 +156,14 @@
                 }
                 makeIntentSenderCanceled(rec);
                 mIntentSenderRecords.remove(key);
+                decrementUidStatLocked(rec);
             }
             if (noCreate) {
                 return rec;
             }
             rec = new PendingIntentRecord(this, key, callingUid);
             mIntentSenderRecords.put(key, rec.ref);
+            incrementUidStatLocked(rec);
             return rec;
         }
     }
@@ -198,6 +220,7 @@
                 didSomething = true;
                 it.remove();
                 makeIntentSenderCanceled(pir);
+                decrementUidStatLocked(pir);
                 if (pir.key.activity != null) {
                     final Message m = PooledLambda.obtainMessage(
                             PendingIntentController::clearPendingResultForActivity, this,
@@ -237,6 +260,7 @@
         synchronized (mLock) {
             makeIntentSenderCanceled(rec);
             mIntentSenderRecords.remove(rec.key);
+            decrementUidStatLocked(rec);
             if (cleanActivity && rec.key.activity != null) {
                 final Message m = PooledLambda.obtainMessage(
                         PendingIntentController::clearPendingResultForActivity, this,
@@ -369,9 +393,81 @@
                 }
             }
 
+            final int sizeOfIntentsPerUid = mIntentsPerUid.size();
+            if (sizeOfIntentsPerUid > 0) {
+                for (int i = 0; i < sizeOfIntentsPerUid; i++) {
+                    pw.print("  * UID: ");
+                    pw.print(mIntentsPerUid.keyAt(i));
+                    pw.print(" total: ");
+                    pw.println(mIntentsPerUid.valueAt(i));
+                }
+            }
+
             if (!printed) {
                 pw.println("  (nothing)");
             }
         }
     }
+
+    /**
+     * Increment the number of the PendingIntentRecord for the given uid, log a warning
+     * if there are too many for this uid already.
+     */
+    @GuardedBy("mLock")
+    void incrementUidStatLocked(final PendingIntentRecord pir) {
+        final int uid = pir.uid;
+        final int idx = mIntentsPerUid.indexOfKey(uid);
+        int newCount = 1;
+        if (idx >= 0) {
+            newCount = mIntentsPerUid.valueAt(idx) + 1;
+            mIntentsPerUid.setValueAt(idx, newCount);
+        } else {
+            mIntentsPerUid.put(uid, newCount);
+        }
+
+        // If the number is within the range [threshold - N + 1, threshold], log it into buffer
+        final int lowBound = mConstants.PENDINGINTENT_WARNING_THRESHOLD - RECENT_N + 1;
+        RingBuffer<String> recentHistory = null;
+        if (newCount == lowBound) {
+            recentHistory = new RingBuffer(String.class, RECENT_N);
+            mRecentIntentsPerUid.put(uid, recentHistory);
+        } else if (newCount > lowBound && newCount <= mConstants.PENDINGINTENT_WARNING_THRESHOLD) {
+            recentHistory = mRecentIntentsPerUid.get(uid);
+        }
+        if (recentHistory == null) {
+            return;
+        }
+
+        recentHistory.append(pir.key.toString());
+
+        // Output the log if we are hitting the threshold
+        if (newCount == mConstants.PENDINGINTENT_WARNING_THRESHOLD) {
+            Slog.wtf(TAG, "Too many PendingIntent created for uid " + uid
+                    + ", recent " + RECENT_N + ": " + Arrays.toString(recentHistory.toArray()));
+            // Clear the buffer, as we don't want to spam the log when the numbers
+            // are jumping up and down around the threshold.
+            mRecentIntentsPerUid.remove(uid);
+        }
+    }
+
+    /**
+     * Decrement the number of the PendingIntentRecord for the given uid.
+     */
+    @GuardedBy("mLock")
+    void decrementUidStatLocked(final PendingIntentRecord pir) {
+        final int uid = pir.uid;
+        final int idx = mIntentsPerUid.indexOfKey(uid);
+        if (idx >= 0) {
+            final int newCount = mIntentsPerUid.valueAt(idx) - 1;
+            // If we are going below the low threshold, no need to keep logs.
+            if (newCount == mConstants.PENDINGINTENT_WARNING_THRESHOLD - RECENT_N) {
+                mRecentIntentsPerUid.delete(uid);
+            }
+            if (newCount == 0) {
+                mIntentsPerUid.removeAt(idx);
+            } else {
+                mIntentsPerUid.setValueAt(idx, newCount);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index d54d2d7..1997dbd 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -187,7 +187,8 @@
                 + " intent="
                 + (requestIntent != null
                         ? requestIntent.toShortString(false, true, false, false) : "<null>")
-                + " flags=0x" + Integer.toHexString(flags) + " u=" + userId + "}";
+                + " flags=0x" + Integer.toHexString(flags) + " u=" + userId + "}"
+                + " requestCode=" + requestCode;
         }
 
         String typeName() {
@@ -499,6 +500,7 @@
             WeakReference<PendingIntentRecord> current = controller.mIntentSenderRecords.get(key);
             if (current == ref) {
                 controller.mIntentSenderRecords.remove(key);
+                controller.decrementUidStatLocked(this);
             }
         }
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index b84d322..75ab33d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -17,11 +17,18 @@
 package com.android.server.hdmi;
 
 import android.hardware.hdmi.HdmiPortInfo;
+import android.hardware.tv.cec.V1_0.CecMessage;
+import android.hardware.tv.cec.V1_0.HotplugEvent;
+import android.hardware.tv.cec.V1_0.IHdmiCec;
+import android.hardware.tv.cec.V1_0.IHdmiCec.getPhysicalAddressCallback;
+import android.hardware.tv.cec.V1_0.IHdmiCecCallback;
 import android.hardware.tv.cec.V1_0.Result;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 import android.os.Handler;
+import android.os.IHwBinder;
 import android.os.Looper;
 import android.os.MessageQueue;
+import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -79,6 +86,11 @@
 
     private static final int MAX_HDMI_MESSAGE_HISTORY = 250;
 
+    private static final int INVALID_PHYSICAL_ADDRESS = 0xFFFF;
+
+    /** Cookie for matching the right end point. */
+    protected static final int HDMI_CEC_HAL_DEATH_COOKIE = 353;
+
     // Predicate for whether the given logical address is remote device's one or not.
     private final Predicate<Integer> mRemoteDeviceAddressPredicate = new Predicate<Integer>() {
         @Override
@@ -102,10 +114,6 @@
     // device or issued by internal state change.
     private Handler mControlHandler;
 
-    // Stores the pointer to the native implementation of the service that
-    // interacts with HAL.
-    private volatile long mNativePtr;
-
     private final HdmiControlService mService;
 
     // Stores the local CEC devices in the system. Device type is used for key.
@@ -149,23 +157,21 @@
      * A factory method with injection of native methods for testing.
      */
     static HdmiCecController createWithNativeWrapper(
-        HdmiControlService service, NativeWrapper nativeWrapper) {
-            HdmiCecController controller = new HdmiCecController(service, nativeWrapper);
-            long nativePtr = nativeWrapper
-                .nativeInit(controller, service.getServiceLooper().getQueue());
-            if (nativePtr == 0L) {
-                controller = null;
-                return null;
-            }
-
-            controller.init(nativePtr);
-            return controller;
+            HdmiControlService service, NativeWrapper nativeWrapper) {
+        HdmiCecController controller = new HdmiCecController(service, nativeWrapper);
+        String nativePtr = nativeWrapper.nativeInit();
+        if (nativePtr == null) {
+            HdmiLogger.warning("Couldn't get tv.cec service.");
+            return null;
+        }
+        controller.init(nativeWrapper);
+        return controller;
     }
 
-    private void init(long nativePtr) {
+    private void init(NativeWrapper nativeWrapper) {
         mIoHandler = new Handler(mService.getIoLooper());
         mControlHandler = new Handler(mService.getServiceLooper());
-        mNativePtr = nativePtr;
+        nativeWrapper.setCallback(new HdmiCecCallback());
     }
 
     @ServiceThreadOnly
@@ -261,7 +267,7 @@
 
 
     HdmiPortInfo[] getPortInfos() {
-        return mNativeWrapperImpl.nativeGetPortInfos(mNativePtr);
+        return mNativeWrapperImpl.nativeGetPortInfos();
     }
 
     /**
@@ -289,7 +295,7 @@
     int addLogicalAddress(int newLogicalAddress) {
         assertRunOnServiceThread();
         if (HdmiUtils.isValidAddress(newLogicalAddress)) {
-            return mNativeWrapperImpl.nativeAddLogicalAddress(mNativePtr, newLogicalAddress);
+            return mNativeWrapperImpl.nativeAddLogicalAddress(newLogicalAddress);
         } else {
             return Result.FAILURE_INVALID_ARGS;
         }
@@ -306,7 +312,7 @@
         for (int i = 0; i < mLocalDevices.size(); ++i) {
             mLocalDevices.valueAt(i).clearAddress();
         }
-        mNativeWrapperImpl.nativeClearLogicalAddress(mNativePtr);
+        mNativeWrapperImpl.nativeClearLogicalAddress();
     }
 
     @ServiceThreadOnly
@@ -326,7 +332,7 @@
     @ServiceThreadOnly
     int getPhysicalAddress() {
         assertRunOnServiceThread();
-        return mNativeWrapperImpl.nativeGetPhysicalAddress(mNativePtr);
+        return mNativeWrapperImpl.nativeGetPhysicalAddress();
     }
 
     /**
@@ -337,7 +343,7 @@
     @ServiceThreadOnly
     int getVersion() {
         assertRunOnServiceThread();
-        return mNativeWrapperImpl.nativeGetVersion(mNativePtr);
+        return mNativeWrapperImpl.nativeGetVersion();
     }
 
     /**
@@ -348,7 +354,7 @@
     @ServiceThreadOnly
     int getVendorId() {
         assertRunOnServiceThread();
-        return mNativeWrapperImpl.nativeGetVendorId(mNativePtr);
+        return mNativeWrapperImpl.nativeGetVendorId();
     }
 
     /**
@@ -361,7 +367,7 @@
     void setOption(int flag, boolean enabled) {
         assertRunOnServiceThread();
         HdmiLogger.debug("setOption: [flag:%d, enabled:%b]", flag, enabled);
-        mNativeWrapperImpl.nativeSetOption(mNativePtr, flag, enabled);
+        mNativeWrapperImpl.nativeSetOption(flag, enabled);
     }
 
     /**
@@ -375,7 +381,7 @@
         if (!LanguageTag.isLanguage(language)) {
             return;
         }
-        mNativeWrapperImpl.nativeSetLanguage(mNativePtr, language);
+        mNativeWrapperImpl.nativeSetLanguage(language);
     }
 
     /**
@@ -387,7 +393,7 @@
     @ServiceThreadOnly
     void enableAudioReturnChannel(int port, boolean enabled) {
         assertRunOnServiceThread();
-        mNativeWrapperImpl.nativeEnableAudioReturnChannel(mNativePtr, port, enabled);
+        mNativeWrapperImpl.nativeEnableAudioReturnChannel(port, enabled);
     }
 
     /**
@@ -399,7 +405,7 @@
     @ServiceThreadOnly
     boolean isConnected(int port) {
         assertRunOnServiceThread();
-        return mNativeWrapperImpl.nativeIsConnected(mNativePtr, port);
+        return mNativeWrapperImpl.nativeIsConnected(port);
     }
 
     /**
@@ -521,7 +527,7 @@
             // <Polling Message> is a message which has empty body.
             int ret =
                     mNativeWrapperImpl.nativeSendCecCommand(
-                        mNativePtr, sourceAddress, destinationAddress, EMPTY_BODY);
+                        sourceAddress, destinationAddress, EMPTY_BODY);
             if (ret == SendMessageResult.SUCCESS) {
                 return true;
             } else if (ret != SendMessageResult.NACK) {
@@ -627,7 +633,7 @@
                 int i = 0;
                 int errorCode = SendMessageResult.SUCCESS;
                 do {
-                    errorCode = mNativeWrapperImpl.nativeSendCecCommand(mNativePtr,
+                    errorCode = mNativeWrapperImpl.nativeSendCecCommand(
                         cecMessage.getSource(), cecMessage.getDestination(), body);
                     if (errorCode == SendMessageResult.SUCCESS) {
                         break;
@@ -651,7 +657,7 @@
     }
 
     /**
-     * Called by native when incoming CEC message arrived.
+     * Called when incoming CEC message arrived.
      */
     @ServiceThreadOnly
     private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
@@ -663,7 +669,7 @@
     }
 
     /**
-     * Called by native when a hotplug event issues.
+     * Called when a hotplug event issues.
      */
     @ServiceThreadOnly
     private void handleHotplug(int port, boolean connected) {
@@ -710,18 +716,19 @@
     }
 
     protected interface NativeWrapper {
-        long nativeInit(HdmiCecController handler, MessageQueue messageQueue);
-        int nativeSendCecCommand(long controllerPtr, int srcAddress, int dstAddress, byte[] body);
-        int nativeAddLogicalAddress(long controllerPtr, int logicalAddress);
-        void nativeClearLogicalAddress(long controllerPtr);
-        int nativeGetPhysicalAddress(long controllerPtr);
-        int nativeGetVersion(long controllerPtr);
-        int nativeGetVendorId(long controllerPtr);
-        HdmiPortInfo[] nativeGetPortInfos(long controllerPtr);
-        void nativeSetOption(long controllerPtr, int flag, boolean enabled);
-        void nativeSetLanguage(long controllerPtr, String language);
-        void nativeEnableAudioReturnChannel(long controllerPtr, int port, boolean flag);
-        boolean nativeIsConnected(long controllerPtr, int port);
+        String nativeInit();
+        void setCallback(HdmiCecCallback callback);
+        int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body);
+        int nativeAddLogicalAddress(int logicalAddress);
+        void nativeClearLogicalAddress();
+        int nativeGetPhysicalAddress();
+        int nativeGetVersion();
+        int nativeGetVendorId();
+        HdmiPortInfo[] nativeGetPortInfos();
+        void nativeSetOption(int flag, boolean enabled);
+        void nativeSetLanguage(String language);
+        void nativeEnableAudioReturnChannel(int port, boolean flag);
+        boolean nativeIsConnected(int port);
     }
 
     private static native long nativeInit(HdmiCecController handler, MessageQueue messageQueue);
@@ -739,67 +746,200 @@
         int port, boolean flag);
     private static native boolean nativeIsConnected(long controllerPtr, int port);
 
-    private static final class NativeWrapperImpl implements NativeWrapper {
+    private static final class NativeWrapperImpl implements NativeWrapper,
+            IHwBinder.DeathRecipient, getPhysicalAddressCallback {
+        private IHdmiCec mHdmiCec;
+        private final Object mLock = new Object();
+        private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
 
         @Override
-        public long nativeInit(HdmiCecController handler, MessageQueue messageQueue) {
-            return HdmiCecController.nativeInit(handler, messageQueue);
+        public String nativeInit() {
+            return (connectToHal() ? mHdmiCec.toString() : null);
+        }
+
+        boolean connectToHal() {
+            try {
+                mHdmiCec = IHdmiCec.getService();
+                try {
+                    mHdmiCec.linkToDeath(this, HDMI_CEC_HAL_DEATH_COOKIE);
+                } catch (RemoteException e) {
+                    HdmiLogger.error("Couldn't link to death : ", e);
+                }
+            } catch (RemoteException e) {
+                HdmiLogger.error("Couldn't get tv.cec service : ", e);
+                return false;
+            }
+            return true;
         }
 
         @Override
-        public int nativeSendCecCommand(long controllerPtr, int srcAddress, int dstAddress,
-            byte[] body) {
-            return HdmiCecController.nativeSendCecCommand(controllerPtr, srcAddress, dstAddress, body);
+        public void setCallback(HdmiCecCallback callback) {
+            try {
+                mHdmiCec.setCallback(callback);
+            } catch (RemoteException e) {
+                HdmiLogger.error("Couldn't initialise tv.cec callback : ", e);
+            }
         }
 
         @Override
-        public int nativeAddLogicalAddress(long controllerPtr, int logicalAddress) {
-            return HdmiCecController.nativeAddLogicalAddress(controllerPtr, logicalAddress);
+        public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) {
+            CecMessage message = new CecMessage();
+            message.initiator = srcAddress;
+            message.destination = dstAddress;
+            message.body = new ArrayList<>(body.length);
+            for (byte b : body) {
+                message.body.add(b);
+            }
+            try {
+                return mHdmiCec.sendMessage(message);
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to send CEC message : ", e);
+                return SendMessageResult.FAIL;
+            }
         }
 
         @Override
-        public void nativeClearLogicalAddress(long controllerPtr) {
-            HdmiCecController.nativeClearLogicalAddress(controllerPtr);
+        public int nativeAddLogicalAddress(int logicalAddress) {
+            try {
+                return mHdmiCec.addLogicalAddress(logicalAddress);
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to add a logical address : ", e);
+                return Result.FAILURE_INVALID_ARGS;
+            }
         }
 
         @Override
-        public int nativeGetPhysicalAddress(long controllerPtr) {
-            return HdmiCecController.nativeGetPhysicalAddress(controllerPtr);
+        public void nativeClearLogicalAddress() {
+            try {
+                mHdmiCec.clearLogicalAddress();
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to clear logical address : ", e);
+            }
         }
 
         @Override
-        public int nativeGetVersion(long controllerPtr) {
-            return HdmiCecController.nativeGetVersion(controllerPtr);
+        public int nativeGetPhysicalAddress() {
+            try {
+                mHdmiCec.getPhysicalAddress(this);
+                return mPhysicalAddress;
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to get physical address : ", e);
+                return INVALID_PHYSICAL_ADDRESS;
+            }
         }
 
         @Override
-        public int nativeGetVendorId(long controllerPtr) {
-            return HdmiCecController.nativeGetVendorId(controllerPtr);
+        public int nativeGetVersion() {
+            try {
+                return mHdmiCec.getCecVersion();
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to get cec version : ", e);
+                return Result.FAILURE_UNKNOWN;
+            }
         }
 
         @Override
-        public HdmiPortInfo[] nativeGetPortInfos(long controllerPtr) {
-            return HdmiCecController.nativeGetPortInfos(controllerPtr);
+        public int nativeGetVendorId() {
+            try {
+                return mHdmiCec.getVendorId();
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to get vendor id : ", e);
+                return Result.FAILURE_UNKNOWN;
+            }
         }
 
         @Override
-        public void nativeSetOption(long controllerPtr, int flag, boolean enabled) {
-            HdmiCecController.nativeSetOption(controllerPtr, flag, enabled);
+        public HdmiPortInfo[] nativeGetPortInfos() {
+            try {
+                ArrayList<android.hardware.tv.cec.V1_0.HdmiPortInfo> hdmiPortInfos =
+                        mHdmiCec.getPortInfo();
+                HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.size()];
+                int i = 0;
+                for (android.hardware.tv.cec.V1_0.HdmiPortInfo portInfo : hdmiPortInfos) {
+                    hdmiPortInfo[i] = new HdmiPortInfo(portInfo.portId,
+                            portInfo.type,
+                            portInfo.physicalAddress,
+                            portInfo.cecSupported,
+                            false,
+                            portInfo.arcSupported);
+                    i++;
+                }
+                return hdmiPortInfo;
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to get port information : ", e);
+                return null;
+            }
         }
 
         @Override
-        public void nativeSetLanguage(long controllerPtr, String language) {
-            HdmiCecController.nativeSetLanguage(controllerPtr, language);
+        public void nativeSetOption(int flag, boolean enabled) {
+            try {
+                mHdmiCec.setOption(flag, enabled);
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to set option : ", e);
+            }
         }
 
         @Override
-        public void nativeEnableAudioReturnChannel(long controllerPtr, int port, boolean flag) {
-            HdmiCecController.nativeEnableAudioReturnChannel(controllerPtr, port, flag);
+        public void nativeSetLanguage(String language) {
+            try {
+                mHdmiCec.setLanguage(language);
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to set language : ", e);
+            }
         }
 
         @Override
-        public boolean nativeIsConnected(long controllerPtr, int port) {
-            return HdmiCecController.nativeIsConnected(controllerPtr, port);
+        public void nativeEnableAudioReturnChannel(int port, boolean flag) {
+            try {
+                mHdmiCec.enableAudioReturnChannel(port, flag);
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to enable/disable ARC : ", e);
+            }
+        }
+
+        @Override
+        public boolean nativeIsConnected(int port) {
+            try {
+                return mHdmiCec.isConnected(port);
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to get connection info : ", e);
+                return false;
+            }
+        }
+
+        @Override
+        public void serviceDied(long cookie) {
+            if (cookie == HDMI_CEC_HAL_DEATH_COOKIE) {
+                HdmiLogger.error(TAG, "Service died cokkie : " + cookie + "; reconnecting");
+                connectToHal();
+            }
+        }
+
+        @Override
+        public void onValues(int result, short addr) {
+            if (result == Result.SUCCESS) {
+                synchronized (mLock) {
+                    mPhysicalAddress = new Short(addr).intValue();
+                }
+            }
+        }
+    }
+
+    final class HdmiCecCallback extends IHdmiCecCallback.Stub {
+        @Override
+        public void onCecMessage(CecMessage message) throws RemoteException {
+            byte[] body = new byte[message.body.size()];
+            for (int i = 0; i < message.body.size(); i++) {
+                body[i] = message.body.get(i);
+            }
+            runOnServiceThread(
+                    () -> handleIncomingCecCommand(message.initiator, message.destination, body));
+        }
+
+        @Override
+        public void onHotplugEvent(HotplugEvent event) throws RemoteException {
+            runOnServiceThread(() -> handleHotplug(event.portId, event.connected));
         }
     }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiLogger.java b/services/core/java/com/android/server/hdmi/HdmiLogger.java
index 2309293..8da3c93 100644
--- a/services/core/java/com/android/server/hdmi/HdmiLogger.java
+++ b/services/core/java/com/android/server/hdmi/HdmiLogger.java
@@ -18,9 +18,9 @@
 
 import android.annotation.Nullable;
 import android.os.SystemClock;
+import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
-import android.util.Log;
 
 import java.util.HashMap;
 
@@ -71,6 +71,10 @@
         getLogger().errorInternal(toLogString(logMessage, objs));
     }
 
+    static void error(String logMessage, Exception e, Object... objs) {
+        getLogger().errorInternal(toLogString(logMessage + e, objs));
+    }
+
     private void errorInternal(String logMessage) {
         String log = updateLog(mErrorTimingCache, logMessage);
         if (!log.isEmpty()) {
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index d8acf0e..85544d0 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -678,8 +678,6 @@
         mNetworkConnectivityHandler = new GnssNetworkConnectivityHandler(context,
                 GnssLocationProvider.this::onNetworkAvailable, mLooper, mNIHandler);
 
-        sendMessage(INITIALIZE_HANDLER, 0, null);
-
         mGnssStatusListenerHelper = new GnssStatusListenerHelper(mContext, mHandler) {
             @Override
             protected boolean isAvailableInPlatform() {
@@ -746,6 +744,8 @@
 
         setProperties(PROPERTIES);
         setAllowed(true);
+
+        sendMessage(INITIALIZE_HANDLER, 0, null);
     }
 
     /**
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 2721678..f882c57 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -62,6 +62,7 @@
 
     public abstract void setRouteVolume(long requestId, String routeId, int volume);
     public abstract void setSessionVolume(long requestId, String sessionId, int volume);
+    public abstract void prepareReleaseSession(@NonNull String sessionId);
 
     @NonNull
     public String getUniqueId() {
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index d6b98e2..85af346 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -34,11 +34,16 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -61,6 +66,9 @@
 
     private RouteDiscoveryPreference mLastDiscoveryPreference = null;
 
+    @GuardedBy("mLock")
+    final List<RoutingSessionInfo> mReleasingSessions = new ArrayList<>();
+
     MediaRoute2ProviderServiceProxy(@NonNull Context context, @NonNull ComponentName componentName,
             int userId) {
         super(componentName);
@@ -141,6 +149,19 @@
         }
     }
 
+    @Override
+    public void prepareReleaseSession(@NonNull String sessionId) {
+        synchronized (mLock) {
+            for (RoutingSessionInfo session : mSessionInfos) {
+                if (TextUtils.equals(session.getId(), sessionId)) {
+                    mSessionInfos.remove(session);
+                    mReleasingSessions.add(session);
+                    break;
+                }
+            }
+        }
+    }
+
     public boolean hasComponentName(String packageName, String className) {
         return mComponentName.getPackageName().equals(packageName)
                 && mComponentName.getClassName().equals(className);
@@ -300,88 +321,97 @@
     }
 
     private void onSessionCreated(Connection connection, long requestId,
-            RoutingSessionInfo sessionInfo) {
+            RoutingSessionInfo newSession) {
         if (mActiveConnection != connection) {
             return;
         }
 
-        if (sessionInfo == null) {
-            Slog.w(TAG, "onSessionCreated: Ignoring null sessionInfo sent from " + mComponentName);
+        if (newSession == null) {
+            Slog.w(TAG, "onSessionCreated: Ignoring null session sent from " + mComponentName);
             return;
         }
 
-        sessionInfo = updateSessionInfo(sessionInfo);
+        newSession = assignProviderIdForSession(newSession);
+        String newSessionId = newSession.getId();
 
-        boolean duplicateSessionAlreadyExists = false;
         synchronized (mLock) {
-            for (int i = 0; i < mSessionInfos.size(); i++) {
-                if (mSessionInfos.get(i).getId().equals(sessionInfo.getId())) {
-                    duplicateSessionAlreadyExists = true;
-                    break;
-                }
+            if (mSessionInfos.stream()
+                    .anyMatch(session -> TextUtils.equals(session.getId(), newSessionId))
+                    || mReleasingSessions.stream()
+                    .anyMatch(session -> TextUtils.equals(session.getId(), newSessionId))) {
+                Slog.w(TAG, "onSessionCreated: Duplicate session already exists. Ignoring.");
+                return;
             }
-            mSessionInfos.add(sessionInfo);
+            mSessionInfos.add(newSession);
         }
 
-        if (duplicateSessionAlreadyExists) {
-            Slog.w(TAG, "onSessionCreated: Duplicate session already exists. Ignoring.");
-            return;
-        }
-
-        mCallback.onSessionCreated(this, requestId, sessionInfo);
+        mCallback.onSessionCreated(this, requestId, newSession);
     }
 
-    private void onSessionUpdated(Connection connection, RoutingSessionInfo sessionInfo) {
+    private void onSessionUpdated(Connection connection, RoutingSessionInfo updatedSession) {
         if (mActiveConnection != connection) {
             return;
         }
-        if (sessionInfo == null) {
-            Slog.w(TAG, "onSessionUpdated: Ignoring null sessionInfo sent from "
+        if (updatedSession == null) {
+            Slog.w(TAG, "onSessionUpdated: Ignoring null session sent from "
                     + mComponentName);
             return;
         }
 
-        sessionInfo = updateSessionInfo(sessionInfo);
+        updatedSession = assignProviderIdForSession(updatedSession);
 
         boolean found = false;
         synchronized (mLock) {
             for (int i = 0; i < mSessionInfos.size(); i++) {
-                if (mSessionInfos.get(i).getId().equals(sessionInfo.getId())) {
-                    mSessionInfos.set(i, sessionInfo);
+                if (mSessionInfos.get(i).getId().equals(updatedSession.getId())) {
+                    mSessionInfos.set(i, updatedSession);
                     found = true;
                     break;
                 }
             }
+
+            if (!found) {
+                for (RoutingSessionInfo releasingSession : mReleasingSessions) {
+                    if (TextUtils.equals(releasingSession.getId(), updatedSession.getId())) {
+                        return;
+                    }
+                }
+                Slog.w(TAG, "onSessionUpdated: Matching session info not found");
+                return;
+            }
         }
 
-        if (!found) {
-            Slog.w(TAG, "onSessionUpdated: Matching session info not found");
-            return;
-        }
-
-        mCallback.onSessionUpdated(this, sessionInfo);
+        mCallback.onSessionUpdated(this, updatedSession);
     }
 
-    private void onSessionReleased(Connection connection, RoutingSessionInfo sessionInfo) {
+    private void onSessionReleased(Connection connection, RoutingSessionInfo releaedSession) {
         if (mActiveConnection != connection) {
             return;
         }
-        if (sessionInfo == null) {
-            Slog.w(TAG, "onSessionReleased: Ignoring null sessionInfo sent from " + mComponentName);
+        if (releaedSession == null) {
+            Slog.w(TAG, "onSessionReleased: Ignoring null session sent from " + mComponentName);
             return;
         }
 
-        sessionInfo = updateSessionInfo(sessionInfo);
+        releaedSession = assignProviderIdForSession(releaedSession);
 
         boolean found = false;
         synchronized (mLock) {
-            for (int i = 0; i < mSessionInfos.size(); i++) {
-                if (mSessionInfos.get(i).getId().equals(sessionInfo.getId())) {
-                    mSessionInfos.remove(i);
+            for (RoutingSessionInfo session : mSessionInfos) {
+                if (TextUtils.equals(session.getId(), releaedSession.getId())) {
+                    mSessionInfos.remove(session);
                     found = true;
                     break;
                 }
             }
+            if (!found) {
+                for (RoutingSessionInfo session : mReleasingSessions) {
+                    if (TextUtils.equals(session.getId(), releaedSession.getId())) {
+                        mReleasingSessions.remove(session);
+                        return;
+                    }
+                }
+            }
         }
 
         if (!found) {
@@ -389,10 +419,10 @@
             return;
         }
 
-        mCallback.onSessionReleased(this, sessionInfo);
+        mCallback.onSessionReleased(this, releaedSession);
     }
 
-    private RoutingSessionInfo updateSessionInfo(RoutingSessionInfo sessionInfo) {
+    private RoutingSessionInfo assignProviderIdForSession(RoutingSessionInfo sessionInfo) {
         return new RoutingSessionInfo.Builder(sessionInfo)
                 .setOwnerPackageName(mComponentName.getPackageName())
                 .setProviderId(getUniqueId())
@@ -423,6 +453,7 @@
                     mCallback.onSessionReleased(this, sessionInfo);
                 }
                 mSessionInfos.clear();
+                mReleasingSessions.clear();
             }
         }
     }
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 72d296f..cc95039 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -16,9 +16,7 @@
 
 package com.android.server.media;
 
-import static android.media.MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE;
 import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
-import static android.media.MediaRoute2ProviderService.REQUEST_ID_NONE;
 import static android.media.MediaRouter2Utils.getOriginalId;
 import static android.media.MediaRouter2Utils.getProviderId;
 
@@ -33,6 +31,8 @@
 import android.media.IMediaRouter2Manager;
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
+import android.media.MediaRoute2ProviderService;
+import android.media.MediaRouter2Manager;
 import android.media.RouteDiscoveryPreference;
 import android.media.RoutingSessionInfo;
 import android.os.Binder;
@@ -235,30 +235,17 @@
     }
 
     public void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId,
+            long managerRequestId, RoutingSessionInfo oldSession,
             MediaRoute2Info route, Bundle sessionHints) {
         Objects.requireNonNull(router, "router must not be null");
+        Objects.requireNonNull(oldSession, "oldSession must not be null");
         Objects.requireNonNull(route, "route must not be null");
 
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                requestCreateSessionWithRouter2Locked(requestId, router, route, sessionHints);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    public void notifySessionHintsForCreatingSession(IMediaRouter2 router,
-            long uniqueRequestId, MediaRoute2Info route, Bundle sessionHints) {
-        Objects.requireNonNull(router, "router must not be null");
-        Objects.requireNonNull(route, "route must not be null");
-
-        final long token = Binder.clearCallingIdentity();
-        try {
-            synchronized (mLock) {
-                notifySessionHintsForCreatingSessionLocked(uniqueRequestId,
-                        router, route, sessionHints);
+                requestCreateSessionWithRouter2Locked(requestId, managerRequestId,
+                        router, oldSession, route, sessionHints);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -417,16 +404,14 @@
     }
 
     public void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId,
-            String packageName, MediaRoute2Info route) {
+            RoutingSessionInfo oldSession, MediaRoute2Info route) {
         Objects.requireNonNull(manager, "manager must not be null");
-        if (TextUtils.isEmpty(packageName)) {
-            throw new IllegalArgumentException("packageName must not be empty");
-        }
+        Objects.requireNonNull(oldSession, "oldSession must not be null");
 
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                requestCreateSessionWithManagerLocked(requestId, manager, packageName, route);
+                requestCreateSessionWithManagerLocked(requestId, manager, oldSession, route);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -638,7 +623,8 @@
         }
     }
 
-    private void requestCreateSessionWithRouter2Locked(int requestId, @NonNull IMediaRouter2 router,
+    private void requestCreateSessionWithRouter2Locked(int requestId, long managerRequestId,
+            @NonNull IMediaRouter2 router, @NonNull RoutingSessionInfo oldSession,
             @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
         final IBinder binder = router.asBinder();
         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
@@ -647,41 +633,60 @@
             return;
         }
 
-        if (route.isSystemRoute() && !routerRecord.mHasModifyAudioRoutingPermission
-                && !TextUtils.equals(route.getId(),
-                routerRecord.mUserRecord.mHandler.mSystemProvider.getDefaultRoute().getId())) {
-            Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to"
-                    + route);
-            routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
-                    routerRecord, requestId);
-            return;
+        if (managerRequestId != MediaRoute2ProviderService.REQUEST_ID_NONE) {
+            ManagerRecord manager = routerRecord.mUserRecord.mHandler.findManagerWithId(
+                    toRequesterId(managerRequestId));
+            if (manager == null || manager.mLastSessionCreationRequest == null) {
+                Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
+                        + "Ignoring unknown request.");
+                routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
+                        routerRecord, requestId);
+                return;
+            }
+            if (!TextUtils.equals(manager.mLastSessionCreationRequest.mOldSession.getId(),
+                    oldSession.getId())) {
+                Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
+                        + "Ignoring unmatched routing session.");
+                routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
+                        routerRecord, requestId);
+                return;
+            }
+            if (!TextUtils.equals(manager.mLastSessionCreationRequest.mRoute.getId(),
+                    route.getId())) {
+                // When media router has no permission
+                if (!routerRecord.mHasModifyAudioRoutingPermission
+                        && manager.mLastSessionCreationRequest.mRoute.isSystemRoute()
+                        && route.isSystemRoute()) {
+                    route = manager.mLastSessionCreationRequest.mRoute;
+                } else {
+                    Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
+                            + "Ignoring unmatched route.");
+                    routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
+                            routerRecord, requestId);
+                    return;
+                }
+            }
+            manager.mLastSessionCreationRequest = null;
+        } else {
+            if (route.isSystemRoute() && !routerRecord.mHasModifyAudioRoutingPermission
+                    && !TextUtils.equals(route.getId(),
+                    routerRecord.mUserRecord.mHandler.mSystemProvider.getDefaultRoute().getId())) {
+                Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to"
+                        + route);
+                routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
+                        routerRecord, requestId);
+                return;
+            }
         }
 
         long uniqueRequestId = toUniqueRequestId(routerRecord.mRouterId, requestId);
         routerRecord.mUserRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::requestCreateSessionWithRouter2OnHandler,
                         routerRecord.mUserRecord.mHandler,
-                        uniqueRequestId, routerRecord, route,
+                        uniqueRequestId, managerRequestId, routerRecord, oldSession, route,
                         sessionHints));
     }
 
-    private void notifySessionHintsForCreatingSessionLocked(long uniqueRequestId,
-            @NonNull IMediaRouter2 router,
-            @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
-        final IBinder binder = router.asBinder();
-        final RouterRecord routerRecord = mAllRouterRecords.get(binder);
-
-        if (routerRecord == null) {
-            Slog.w(TAG, "notifySessionHintsForCreatingSessionLocked: Ignoring unknown router.");
-            return;
-        }
-
-        routerRecord.mUserRecord.mHandler.sendMessage(
-                obtainMessage(UserHandler::requestCreateSessionWithManagerOnHandler,
-                        routerRecord.mUserRecord.mHandler,
-                        uniqueRequestId, routerRecord, route, sessionHints));
-    }
-
     private void selectRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
         final IBinder binder = router.asBinder();
@@ -853,27 +858,46 @@
 
     private void requestCreateSessionWithManagerLocked(int requestId,
             @NonNull IMediaRouter2Manager manager,
-            @NonNull String packageName, @NonNull MediaRoute2Info route) {
+            @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
         ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder());
         if (managerRecord == null) {
             return;
         }
 
+        String packageName = oldSession.getClientPackageName();
+
         RouterRecord routerRecord = managerRecord.mUserRecord.findRouterRecordLocked(packageName);
         if (routerRecord == null) {
             Slog.w(TAG, "requestCreateSessionWithManagerLocked: Ignoring session creation for "
                     + "unknown router.");
+            try {
+                managerRecord.mManager.notifyRequestFailed(requestId, REASON_UNKNOWN_ERROR);
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "requestCreateSessionWithManagerLocked: Failed to notify failure. "
+                        + "Manager probably died.");
+            }
             return;
         }
 
         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
+        if (managerRecord.mLastSessionCreationRequest != null) {
+            managerRecord.mUserRecord.mHandler.notifyRequestFailedToManager(
+                    managerRecord.mManager,
+                    toOriginalRequestId(managerRecord.mLastSessionCreationRequest
+                            .mManagerRequestId),
+                    REASON_UNKNOWN_ERROR);
+            managerRecord.mLastSessionCreationRequest = null;
+        }
+        managerRecord.mLastSessionCreationRequest = new SessionCreationRequest(routerRecord,
+                MediaRoute2ProviderService.REQUEST_ID_NONE, uniqueRequestId,
+                oldSession, route);
 
         // Before requesting to the provider, get session hints from the media router.
         // As a return, media router will request to create a session.
         routerRecord.mUserRecord.mHandler.sendMessage(
-                obtainMessage(UserHandler::getSessionHintsForCreatingSessionOnHandler,
+                obtainMessage(UserHandler::requestRouterCreateSessionOnHandler,
                         routerRecord.mUserRecord.mHandler,
-                        uniqueRequestId, routerRecord, managerRecord, route));
+                        uniqueRequestId, routerRecord, managerRecord, oldSession, route));
     }
 
     private void selectRouteWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager,
@@ -887,7 +911,7 @@
 
         // Can be null if the session is system's or RCN.
         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
-                .findRouterforSessionLocked(uniqueSessionId);
+                .findRouterWithSessionLocked(uniqueSessionId);
 
         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
         managerRecord.mUserRecord.mHandler.sendMessage(
@@ -908,7 +932,7 @@
 
         // Can be null if the session is system's or RCN.
         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
-                .findRouterforSessionLocked(uniqueSessionId);
+                .findRouterWithSessionLocked(uniqueSessionId);
 
         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
         managerRecord.mUserRecord.mHandler.sendMessage(
@@ -929,7 +953,7 @@
 
         // Can be null if the session is system's or RCN.
         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
-                .findRouterforSessionLocked(uniqueSessionId);
+                .findRouterWithSessionLocked(uniqueSessionId);
 
         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
         managerRecord.mUserRecord.mHandler.sendMessage(
@@ -966,7 +990,7 @@
         }
 
         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
-                .findRouterforSessionLocked(uniqueSessionId);
+                .findRouterWithSessionLocked(uniqueSessionId);
 
         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
         managerRecord.mUserRecord.mHandler.sendMessage(
@@ -1097,6 +1121,7 @@
         public final int mPid;
         public final String mPackageName;
         public final int mManagerId;
+        public SessionCreationRequest mLastSessionCreationRequest;
 
         ManagerRecord(UserRecord userRecord, IMediaRouter2Manager manager,
                 int uid, int pid, String packageName) {
@@ -1222,10 +1247,20 @@
         }
 
         @Nullable
-        public RouterRecord findRouterforSessionLocked(@NonNull String uniqueSessionId) {
+        public RouterRecord findRouterWithSessionLocked(@NonNull String uniqueSessionId) {
             return mSessionToRouterMap.get(uniqueSessionId);
         }
 
+        @Nullable
+        public ManagerRecord findManagerWithId(int managerId) {
+            for (ManagerRecord manager : getManagerRecords()) {
+                if (manager.mManagerId == managerId) {
+                    return manager;
+                }
+            }
+            return null;
+        }
+
         private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) {
             int providerInfoIndex = getLastProviderInfoIndex(provider.getUniqueId());
             MediaRoute2ProviderInfo currentInfo = provider.getProviderInfo();
@@ -1318,26 +1353,28 @@
             return -1;
         }
 
-        private void getSessionHintsForCreatingSessionOnHandler(long uniqueRequestId,
+        private void requestRouterCreateSessionOnHandler(long uniqueRequestId,
                 @NonNull RouterRecord routerRecord, @NonNull ManagerRecord managerRecord,
-                @NonNull MediaRoute2Info route) {
-            SessionCreationRequest request =
-                    new SessionCreationRequest(routerRecord, uniqueRequestId, route, managerRecord);
-            mSessionCreationRequests.add(request);
-
+                @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
             try {
-                routerRecord.mRouter.getSessionHintsForCreatingSession(uniqueRequestId, route);
+                if (route.isSystemRoute() && !routerRecord.mHasModifyAudioRoutingPermission) {
+                    routerRecord.mRouter.requestCreateSessionByManager(uniqueRequestId,
+                            oldSession, mSystemProvider.getDefaultRoute());
+                } else {
+                    routerRecord.mRouter.requestCreateSessionByManager(uniqueRequestId,
+                            oldSession, route);
+                }
             } catch (RemoteException ex) {
                 Slog.w(TAG, "getSessionHintsForCreatingSessionOnHandler: "
                         + "Failed to request. Router probably died.", ex);
-                mSessionCreationRequests.remove(request);
                 notifyRequestFailedToManager(managerRecord.mManager,
                         toOriginalRequestId(uniqueRequestId), REASON_UNKNOWN_ERROR);
             }
         }
 
         private void requestCreateSessionWithRouter2OnHandler(long uniqueRequestId,
-                @NonNull RouterRecord routerRecord,
+                long managerRequestId, @NonNull RouterRecord routerRecord,
+                @NonNull RoutingSessionInfo oldSession,
                 @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
 
             final MediaRoute2Provider provider = findProvider(route.getProviderId());
@@ -1350,49 +1387,14 @@
             }
 
             SessionCreationRequest request =
-                    new SessionCreationRequest(routerRecord, uniqueRequestId, route, null);
+                    new SessionCreationRequest(routerRecord, uniqueRequestId,
+                            managerRequestId, oldSession, route);
             mSessionCreationRequests.add(request);
 
             provider.requestCreateSession(uniqueRequestId, routerRecord.mPackageName,
                     route.getOriginalId(), sessionHints);
         }
 
-        private void requestCreateSessionWithManagerOnHandler(long uniqueRequestId,
-                @NonNull RouterRecord routerRecord,
-                @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
-            SessionCreationRequest matchingRequest = null;
-            for (SessionCreationRequest request : mSessionCreationRequests) {
-                if (request.mUniqueRequestId == uniqueRequestId) {
-                    matchingRequest = request;
-                    break;
-                }
-            }
-            if (matchingRequest == null) {
-                Slog.w(TAG, "requestCreateSessionWithManagerOnHandler: "
-                        + "Ignoring an unknown session creation request.");
-                return;
-            }
-
-            if (!TextUtils.equals(matchingRequest.mRoute.getId(), route.getId())) {
-                Slog.w(TAG, "requestCreateSessionWithManagerOnHandler: "
-                        + "The given route is different from the requested route.");
-                return;
-            }
-
-            final MediaRoute2Provider provider = findProvider(route.getProviderId());
-            if (provider == null) {
-                Slog.w(TAG, "requestCreateSessionWithManagerOnHandler: Ignoring session "
-                        + "creation request since no provider found for given route=" + route);
-                mSessionCreationRequests.remove(matchingRequest);
-                notifyRequestFailedToManager(matchingRequest.mRequestedManagerRecord.mManager,
-                        toOriginalRequestId(uniqueRequestId), REASON_ROUTE_NOT_AVAILABLE);
-                return;
-            }
-
-            provider.requestCreateSession(uniqueRequestId, routerRecord.mPackageName,
-                    route.getOriginalId(), sessionHints);
-        }
-
         // routerRecord can be null if the session is system's or RCN.
         private void selectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord,
                 @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
@@ -1539,25 +1541,23 @@
 
         private void onSessionCreatedOnHandler(@NonNull MediaRoute2Provider provider,
                 long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo) {
-            notifySessionCreatedToManagers(getManagers(),
-                    toOriginalRequestId(uniqueRequestId), sessionInfo);
-
-            if (uniqueRequestId == REQUEST_ID_NONE) {
-                // The session is created without any matching request.
-                return;
-            }
-
             SessionCreationRequest matchingRequest = null;
 
             for (SessionCreationRequest request : mSessionCreationRequests) {
                 if (request.mUniqueRequestId == uniqueRequestId
                         && TextUtils.equals(
-                                request.mRoute.getProviderId(), provider.getUniqueId())) {
+                        request.mRoute.getProviderId(), provider.getUniqueId())) {
                     matchingRequest = request;
                     break;
                 }
             }
 
+            long managerRequestId = (matchingRequest == null)
+                    ? MediaRoute2ProviderService.REQUEST_ID_NONE
+                    : matchingRequest.mManagerRequestId;
+            // Managers should know created session even if it's not requested.
+            notifySessionCreatedToManagers(managerRequestId, sessionInfo);
+
             if (matchingRequest == null) {
                 Slog.w(TAG, "Ignoring session creation result for unknown request. "
                         + "uniqueRequestId=" + uniqueRequestId + ", sessionInfo=" + sessionInfo);
@@ -1565,12 +1565,14 @@
             }
 
             mSessionCreationRequests.remove(matchingRequest);
-
-            if (sessionInfo == null) {
-                // Failed
-                notifySessionCreationFailedToRouter(matchingRequest.mRouterRecord,
-                        toOriginalRequestId(uniqueRequestId));
-                return;
+            // Not to show old session
+            MediaRoute2Provider oldProvider =
+                    findProvider(matchingRequest.mOldSession.getProviderId());
+            if (oldProvider != null) {
+                oldProvider.prepareReleaseSession(matchingRequest.mOldSession.getId());
+            } else {
+                Slog.w(TAG, "onSessionCreatedOnHandler: Can't find provider for an old session. "
+                        + "session=" + matchingRequest.mOldSession);
             }
 
             String originalRouteId = matchingRequest.mRoute.getId();
@@ -1645,23 +1647,17 @@
             }
 
             final int requesterId = toRequesterId(uniqueRequestId);
-            for (ManagerRecord manager : getManagerRecords()) {
-                if (manager.mManagerId == requesterId) {
-                    notifyRequestFailedToManager(
-                            manager.mManager, toOriginalRequestId(uniqueRequestId), reason);
-                    return;
-                }
+            ManagerRecord manager = findManagerWithId(requesterId);
+            if (manager != null) {
+                notifyRequestFailedToManager(
+                        manager.mManager, toOriginalRequestId(uniqueRequestId), reason);
+                return;
             }
 
             // Currently, only the manager can get notified of failures.
             // TODO: Notify router too when the related callback is introduced.
         }
 
-        // TODO(b/157873556): Find a way to prevent providers from notifying error on random reqID.
-        //       Possible solutions can be:
-        //       1) Record the other type of requests too (not only session creation request)
-        //       2) Throw exception on providers when they try to notify error on
-        //          random uniqueRequestId.
         private boolean handleSessionCreationRequestFailed(@NonNull MediaRoute2Provider provider,
                 long uniqueRequestId, int reason) {
             // Check whether the failure is about creating a session
@@ -1683,12 +1679,16 @@
 
             // Notify the requester about the failure.
             // The call should be made by either MediaRouter2 or MediaRouter2Manager.
-            if (matchingRequest.mRequestedManagerRecord == null) {
+            if (matchingRequest.mManagerRequestId == MediaRouter2Manager.REQUEST_ID_NONE) {
                 notifySessionCreationFailedToRouter(
                         matchingRequest.mRouterRecord, toOriginalRequestId(uniqueRequestId));
             } else {
-                notifyRequestFailedToManager(matchingRequest.mRequestedManagerRecord.mManager,
-                        toOriginalRequestId(uniqueRequestId), reason);
+                final int requesterId = toRequesterId(matchingRequest.mManagerRequestId);
+                ManagerRecord manager = findManagerWithId(requesterId);
+                if (manager != null) {
+                    notifyRequestFailedToManager(manager.mManager,
+                            toOriginalRequestId(matchingRequest.mManagerRequestId), reason);
+                }
             }
             return true;
         }
@@ -1921,14 +1921,19 @@
             }
         }
 
-        private void notifySessionCreatedToManagers(@NonNull List<IMediaRouter2Manager> managers,
-                int requestId, @NonNull RoutingSessionInfo sessionInfo) {
-            for (IMediaRouter2Manager manager : managers) {
+        private void notifySessionCreatedToManagers(long managerRequestId,
+                @NonNull RoutingSessionInfo session) {
+            int requesterId = toRequesterId(managerRequestId);
+            int originalRequestId = toOriginalRequestId(managerRequestId);
+
+            for (ManagerRecord manager : getManagerRecords()) {
                 try {
-                    manager.notifySessionCreated(requestId, sessionInfo);
+                    manager.mManager.notifySessionCreated(
+                            ((manager.mManagerId == requesterId) ? originalRequestId :
+                                    MediaRouter2Manager.REQUEST_ID_NONE), session);
                 } catch (RemoteException ex) {
                     Slog.w(TAG, "notifySessionCreatedToManagers: "
-                            + "failed to notify. Manager probably died.", ex);
+                            + "Failed to notify. Manager probably died.", ex);
                 }
             }
         }
@@ -2014,7 +2019,7 @@
                 }
                 mUserRecord.mCompositeDiscoveryPreference =
                         new RouteDiscoveryPreference.Builder(discoveryPreferences)
-                        .build();
+                                .build();
             }
             for (MediaRoute2Provider provider : mRouteProviders) {
                 provider.updateDiscoveryPreference(mUserRecord.mCompositeDiscoveryPreference);
@@ -2030,21 +2035,22 @@
             return null;
         }
 
-        final class SessionCreationRequest {
-            public final RouterRecord mRouterRecord;
-            public final long mUniqueRequestId;
-            public final MediaRoute2Info mRoute;
-            public final ManagerRecord mRequestedManagerRecord;
+    }
+    static final class SessionCreationRequest {
+        public final RouterRecord mRouterRecord;
+        public final long mUniqueRequestId;
+        public final long mManagerRequestId;
+        public final RoutingSessionInfo mOldSession;
+        public final MediaRoute2Info mRoute;
 
-            // requestedManagerRecord is not null only when the request is made by manager.
-            SessionCreationRequest(@NonNull RouterRecord routerRecord, long uniqueRequestId,
-                    @NonNull MediaRoute2Info route,
-                    @Nullable ManagerRecord requestedManagerRecord) {
-                mRouterRecord = routerRecord;
-                mUniqueRequestId = uniqueRequestId;
-                mRoute = route;
-                mRequestedManagerRecord = requestedManagerRecord;
-            }
+        SessionCreationRequest(@NonNull RouterRecord routerRecord, long uniqueRequestId,
+                long managerRequestId, @NonNull RoutingSessionInfo oldSession,
+                @NonNull MediaRoute2Info route) {
+            mRouterRecord = routerRecord;
+            mUniqueRequestId = uniqueRequestId;
+            mManagerRequestId = managerRequestId;
+            mOldSession = oldSession;
+            mRoute = route;
         }
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 3337b48..0e52a67 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -481,16 +481,10 @@
     // Binder call
     @Override
     public void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId,
+            long managerRequestId, RoutingSessionInfo oldSession,
             MediaRoute2Info route, Bundle sessionHints) {
-        mService2.requestCreateSessionWithRouter2(router, requestId, route, sessionHints);
-    }
-
-    // Binder call
-    @Override
-    public void notifySessionHintsForCreatingSession(IMediaRouter2 router,
-            long uniqueRequestId, MediaRoute2Info route, Bundle sessionHints) {
-        mService2.notifySessionHintsForCreatingSession(router,
-                uniqueRequestId, route, sessionHints);
+        mService2.requestCreateSessionWithRouter2(router, requestId, managerRequestId,
+                oldSession, route, sessionHints);
     }
 
     // Binder call
@@ -558,8 +552,8 @@
     // Binder call
     @Override
     public void requestCreateSessionWithManager(IMediaRouter2Manager manager,
-            int requestId, String packageName, MediaRoute2Info route) {
-        mService2.requestCreateSessionWithManager(manager, requestId, packageName, route);
+            int requestId, RoutingSessionInfo oldSession, MediaRoute2Info route) {
+        mService2.requestCreateSessionWithManager(manager, requestId, oldSession, route);
     }
 
     // Binder call
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 42d4c88..2c089ca 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -222,6 +222,11 @@
         // Do nothing since we don't support grouping volume yet.
     }
 
+    @Override
+    public void prepareReleaseSession(String sessionId) {
+        // Do nothing since the system session persists.
+    }
+
     public MediaRoute2Info getDefaultRoute() {
         return mDefaultRoute;
     }
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 8a7702e..3a203d5 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -280,11 +280,15 @@
 
         @Override
         public void onCompatChange(String packageName) {
-            updateEnabledState(mPmInternal.getPackage(packageName));
+            AndroidPackage pkg = mPmInternal.getPackage(packageName);
+            if (pkg == null) {
+                return;
+            }
+            updateEnabledState(pkg);
             mAppsFilter.updateShouldFilterCacheForPackage(packageName);
         }
 
-        private void updateEnabledState(AndroidPackage pkg) {
+        private void updateEnabledState(@NonNull AndroidPackage pkg) {
             // TODO(b/135203078): Do not use toAppInfo
             final boolean enabled = mInjector.getCompatibility().isChangeEnabledInternal(
                     PackageManager.FILTER_APPLICATION_QUERY, pkg.toAppInfoWithoutState());
@@ -297,12 +301,12 @@
 
         @Override
         public void updatePackageState(PackageSetting setting, boolean removed) {
-            final boolean enableLogging =
+            final boolean enableLogging = setting.pkg != null &&
                     !removed && (setting.pkg.isTestOnly() || setting.pkg.isDebuggable());
             enableLogging(setting.appId, enableLogging);
             if (removed) {
-                mDisabledPackages.remove(setting.pkg.getPackageName());
-            } else {
+                mDisabledPackages.remove(setting.name);
+            } else if (setting.pkg != null) {
                 updateEnabledState(setting.pkg);
             }
         }
@@ -495,10 +499,15 @@
      * Adds a package that should be considered when filtering visibility between apps.
      *
      * @param newPkgSetting the new setting being added
+     * @param isReplace if the package is being replaced and may need extra cleanup.
      */
-    public void addPackage(PackageSetting newPkgSetting) {
+    public void addPackage(PackageSetting newPkgSetting, boolean isReplace) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "filter.addPackage");
         try {
+            if (isReplace) {
+                // let's first remove any prior rules for this package
+                removePackage(newPkgSetting);
+            }
             mStateProvider.runWithState((settings, users) -> {
                 addPackageInternal(newPkgSetting, settings);
                 if (mShouldFilterCache != null) {
@@ -578,8 +587,9 @@
                 }
             }
             // if either package instruments the other, mark both as visible to one another
-            if (pkgInstruments(newPkgSetting, existingSetting)
-                    || pkgInstruments(existingSetting, newPkgSetting)) {
+            if (newPkgSetting.pkg != null && existingSetting.pkg != null
+                    && (pkgInstruments(newPkgSetting.pkg, existingSetting.pkg)
+                    || pkgInstruments(existingSetting.pkg, newPkgSetting.pkg))) {
                 mQueriesViaPackage.add(newPkgSetting.appId, existingSetting.appId);
                 mQueriesViaPackage.add(existingSetting.appId, newPkgSetting.appId);
             }
@@ -777,12 +787,20 @@
     }
 
     /**
+     * Equivalent to calling {@link #addPackage(PackageSetting, boolean)} with {@code isReplace}
+     * equal to {@code false}.
+     * @see AppsFilter#addPackage(PackageSetting, boolean)
+     */
+    public void addPackage(PackageSetting newPkgSetting) {
+        addPackage(newPkgSetting, false /* isReplace */);
+    }
+
+    /**
      * Removes a package for consideration when filtering visibility between apps.
      *
      * @param setting the setting of the package being removed.
      */
     public void removePackage(PackageSetting setting) {
-        removeAppIdFromVisibilityCache(setting.appId);
         mStateProvider.runWithState((settings, users) -> {
             final int userCount = users.length;
             for (int u = 0; u < userCount; u++) {
@@ -805,17 +823,7 @@
                 mQueriesViaPackage.remove(mQueriesViaPackage.keyAt(i), setting.appId);
             }
 
-            // re-add other shared user members to re-establish visibility between them and other
-            // packages
-            if (setting.sharedUser != null) {
-                for (int i = setting.sharedUser.packages.size() - 1; i >= 0; i--) {
-                    if (setting.sharedUser.packages.valueAt(i) == setting) {
-                        continue;
-                    }
-                    addPackageInternal(
-                            setting.sharedUser.packages.valueAt(i), settings);
-                }
-            }
+            mForceQueryable.remove(setting.appId);
 
             if (setting.pkg != null && !setting.pkg.getProtectedBroadcasts().isEmpty()) {
                 final String removingPackageName = setting.pkg.getPackageName();
@@ -829,6 +837,21 @@
             mOverlayReferenceMapper.removePkg(setting.name);
             mFeatureConfig.updatePackageState(setting, true /*removed*/);
 
+            // After removing all traces of the package, if it's part of a shared user, re-add other
+            // shared user members to re-establish visibility between them and other packages.
+            // NOTE: this must come after all removals from data structures but before we update the
+            //       cache
+            if (setting.sharedUser != null) {
+                for (int i = setting.sharedUser.packages.size() - 1; i >= 0; i--) {
+                    if (setting.sharedUser.packages.valueAt(i) == setting) {
+                        continue;
+                    }
+                    addPackageInternal(
+                            setting.sharedUser.packages.valueAt(i), settings);
+                }
+            }
+
+            removeAppIdFromVisibilityCache(setting.appId);
             if (mShouldFilterCache != null && setting.sharedUser != null) {
                 for (int i = setting.sharedUser.packages.size() - 1; i >= 0; i--) {
                     PackageSetting siblingSetting = setting.sharedUser.packages.valueAt(i);
@@ -840,9 +863,6 @@
                 }
             }
         });
-        mForceQueryable.remove(setting.appId);
-
-
     }
 
     /**
@@ -1091,16 +1111,14 @@
     }
 
     /** Returns {@code true} if the source package instruments the target package. */
-    private static boolean pkgInstruments(PackageSetting source, PackageSetting target) {
+    private static boolean pkgInstruments(
+            @NonNull AndroidPackage source, @NonNull AndroidPackage target) {
         try {
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "pkgInstruments");
-            final String packageName = target.pkg.getPackageName();
-            final List<ParsedInstrumentation> inst = source.pkg.getInstrumentations();
+            final String packageName = target.getPackageName();
+            final List<ParsedInstrumentation> inst = source.getInstrumentations();
             for (int i = ArrayUtils.size(inst) - 1; i >= 0; i--) {
                 if (Objects.equals(inst.get(i).getTargetPackage(), packageName)) {
-                    if (DEBUG_LOGGING) {
-                        log(source, target, "instrumentation");
-                    }
                     return true;
                 }
             }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index dc7ed34..a9d3a07 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -12368,7 +12368,9 @@
             ksms.addScannedPackageLPw(pkg);
 
             mComponentResolver.addAllComponents(pkg, chatty);
-            mAppsFilter.addPackage(pkgSetting);
+            final boolean isReplace =
+                    reconciledPkg.prepareResult != null && reconciledPkg.prepareResult.replace;
+            mAppsFilter.addPackage(pkgSetting, isReplace);
 
             // Don't allow ephemeral applications to define new permissions groups.
             if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 0dc4d13..1a7490e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2281,7 +2281,7 @@
         if (grant) {
             mPermissionManager.grantRuntimePermission(pkg, perm, translatedUserId);
         } else {
-            mPermissionManager.revokeRuntimePermission(pkg, perm, translatedUserId);
+            mPermissionManager.revokeRuntimePermission(pkg, perm, translatedUserId, null);
         }
         return 0;
     }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 1b11e2d..4f0b689 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -29,6 +29,9 @@
 import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
@@ -327,13 +330,17 @@
             mPackageManagerInt.writeSettings(true);
         }
         @Override
-        public void onPermissionRevoked(int uid, int userId) {
+        public void onPermissionRevoked(int uid, int userId, String reason) {
             mOnPermissionChangeListeners.onPermissionsChanged(uid);
 
             // Critical; after this call the application should never have the permission
             mPackageManagerInt.writeSettings(false);
             final int appId = UserHandle.getAppId(uid);
-            mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED));
+            if (reason == null) {
+                mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED));
+            } else {
+                mHandler.post(() -> killUid(appId, userId, reason));
+            }
         }
         @Override
         public void onInstallPermissionRevoked() {
@@ -470,7 +477,7 @@
             IActivityManager am = ActivityManager.getService();
             if (am != null) {
                 try {
-                    am.killUid(appId, userId, reason);
+                    am.killUidForPermissionChange(appId, userId, reason);
                 } catch (RemoteException e) {
                     /* ignore - same process */
                 }
@@ -754,9 +761,9 @@
             flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
             flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
             flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
+            flagValues &= ~FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
+            flagValues &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+            flagValues &= ~FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
             flagValues &= ~PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
         }
 
@@ -1112,13 +1119,13 @@
 
             int queryFlags = 0;
             if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0) {
-                queryFlags |= PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
+                queryFlags |= FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
             }
             if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE) != 0) {
-                queryFlags |= PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
+                queryFlags |= FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
             }
             if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) {
-                queryFlags |=  PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+                queryFlags |=  FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
             }
 
             ArrayList<String> whitelistedPermissions = null;
@@ -1280,8 +1287,8 @@
 
         final long identity = Binder.clearCallingIdentity();
         try {
-            setWhitelistedRestrictedPermissionsForUser(
-                    pkg, userId, permissions, Process.myUid(), flags, mDefaultPermissionCallback);
+            setWhitelistedRestrictedPermissionsForUsers(pkg, new int[]{ userId }, permissions,
+                    Process.myUid(), flags, mDefaultPermissionCallback);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -1526,19 +1533,21 @@
     }
 
     @Override
-    public void revokeRuntimePermission(String packageName, String permName, int userId) {
+    public void revokeRuntimePermission(String packageName, String permName, int userId,
+            String reason) {
         final int callingUid = Binder.getCallingUid();
         final boolean overridePolicy =
                 checkUidPermission(ADJUST_RUNTIME_PERMISSIONS_POLICY, callingUid)
                         == PackageManager.PERMISSION_GRANTED;
 
         revokeRuntimePermissionInternal(permName, packageName, overridePolicy, callingUid, userId,
-                mDefaultPermissionCallback);
+                reason, mDefaultPermissionCallback);
     }
 
     // TODO swap permission name and package name
     private void revokeRuntimePermissionInternal(String permName, String packageName,
-            boolean overridePolicy, int callingUid, final int userId, PermissionCallback callback) {
+            boolean overridePolicy, int callingUid, final int userId, String reason,
+            PermissionCallback callback) {
         if (ApplicationPackageManager.DEBUG_TRACE_PERMISSION_UPDATES
                 && ApplicationPackageManager.shouldTraceGrant(packageName, permName, userId)) {
             Log.i(TAG, "System is revoking " + packageName + " "
@@ -1629,7 +1638,7 @@
 
         if (callback != null) {
             callback.onPermissionRevoked(UserHandle.getUid(userId,
-                    UserHandle.getAppId(pkg.getUid())), userId);
+                    UserHandle.getAppId(pkg.getUid())), userId, reason);
         }
 
         if (bp.isRuntime()) {
@@ -1703,7 +1712,7 @@
                 mDefaultPermissionCallback.onInstallPermissionGranted();
             }
 
-            public void onPermissionRevoked(int uid, int userId) {
+            public void onPermissionRevoked(int uid, int userId, String reason) {
                 revokedPermissions.add(IntPair.of(uid, userId));
 
                 syncUpdatedUsers.add(userId);
@@ -1816,7 +1825,7 @@
             } else if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
                 // Otherwise, reset the permission.
                 revokeRuntimePermissionInternal(permName, packageName, false, Process.SYSTEM_UID,
-                        userId, delayingPermCallback);
+                        userId, null, delayingPermCallback);
             }
         }
 
@@ -2297,7 +2306,7 @@
 
                                 try {
                                     revokeRuntimePermissionInternal(permissionName, packageName,
-                                            false, callingUid, userId, permissionCallback);
+                                            false, callingUid, userId, null, permissionCallback);
                                 } catch (IllegalArgumentException e) {
                                     Slog.e(TAG, "Could not revoke " + permissionName + " from "
                                             + packageName, e);
@@ -2517,8 +2526,8 @@
                         if (permission.isHardOrSoftRestricted()
                                 || permission.isImmutablyRestricted()) {
                             permissionsState.updatePermissionFlags(permission, userId,
-                                    PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT,
-                                    PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT);
+                                    FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT,
+                                    FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT);
                         }
                         if (targetSdkVersion < Build.VERSION_CODES.M) {
                             permissionsState.updatePermissionFlags(permission, userId,
@@ -3756,8 +3765,8 @@
         }
     }
 
-    private void setWhitelistedRestrictedPermissionsForUser(@NonNull AndroidPackage pkg,
-            @UserIdInt int userId, @Nullable List<String> permissions, int callingUid,
+    private void setWhitelistedRestrictedPermissionsForUsers(@NonNull AndroidPackage pkg,
+            @UserIdInt int[] userIds, @Nullable List<String> permissions, int callingUid,
             @PermissionWhitelistFlags int whitelistFlags, PermissionCallback callback) {
         final PermissionsState permissionsState =
                 PackageManagerServiceUtils.getPermissionsState(mPackageManagerInt, pkg);
@@ -3765,95 +3774,102 @@
             return;
         }
 
-        ArraySet<String> oldGrantedRestrictedPermissions = null;
+        SparseArray<ArraySet<String>> oldGrantedRestrictedPermissions = new SparseArray<>();
         boolean updatePermissions = false;
-
         final int permissionCount = pkg.getRequestedPermissions().size();
-        for (int i = 0; i < permissionCount; i++) {
-            final String permissionName = pkg.getRequestedPermissions().get(i);
 
-            final BasePermission bp = mSettings.getPermissionLocked(permissionName);
+        for (int i = 0; i < userIds.length; i++) {
+            int userId = userIds[i];
+            for (int j = 0; j < permissionCount; j++) {
+                final String permissionName = pkg.getRequestedPermissions().get(j);
 
-            if (bp == null || !bp.isHardOrSoftRestricted()) {
-                continue;
-            }
+                final BasePermission bp = mSettings.getPermissionLocked(permissionName);
 
-            if (permissionsState.hasPermission(permissionName, userId)) {
-                if (oldGrantedRestrictedPermissions == null) {
-                    oldGrantedRestrictedPermissions = new ArraySet<>();
+                if (bp == null || !bp.isHardOrSoftRestricted()) {
+                    continue;
                 }
-                oldGrantedRestrictedPermissions.add(permissionName);
-            }
 
-            final int oldFlags = permissionsState.getPermissionFlags(permissionName, userId);
-
-            int newFlags = oldFlags;
-            int mask = 0;
-            int whitelistFlagsCopy = whitelistFlags;
-            while (whitelistFlagsCopy != 0) {
-                final int flag = 1 << Integer.numberOfTrailingZeros(whitelistFlagsCopy);
-                whitelistFlagsCopy &= ~flag;
-                switch (flag) {
-                    case FLAG_PERMISSION_WHITELIST_SYSTEM: {
-                        mask |= PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
-                        if (permissions != null && permissions.contains(permissionName)) {
-                            newFlags |= PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
-                        } else {
-                            newFlags &= ~PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
-                        }
-                    } break;
-                    case FLAG_PERMISSION_WHITELIST_UPGRADE: {
-                        mask |= PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
-                        if (permissions != null && permissions.contains(permissionName)) {
-                            newFlags |= PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
-                        } else {
-                            newFlags &= ~PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
-                        }
-                    } break;
-                    case FLAG_PERMISSION_WHITELIST_INSTALLER: {
-                        mask |= PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
-                        if (permissions != null && permissions.contains(permissionName)) {
-                            newFlags |= PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
-                        } else {
-                            newFlags &= ~PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
-                        }
-                    } break;
+                if (permissionsState.hasPermission(permissionName, userId)) {
+                    if (oldGrantedRestrictedPermissions.get(userId) == null) {
+                        oldGrantedRestrictedPermissions.put(userId, new ArraySet<>());
+                    }
+                    oldGrantedRestrictedPermissions.get(userId).add(permissionName);
                 }
-            }
 
-            if (oldFlags == newFlags) {
-                continue;
-            }
+                final int oldFlags = permissionsState.getPermissionFlags(permissionName, userId);
 
-            updatePermissions = true;
-
-            final boolean wasWhitelisted = (oldFlags
-                    & (PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT)) != 0;
-            final boolean isWhitelisted = (newFlags
-                    & (PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT)) != 0;
-
-            // If the permission is policy fixed as granted but it is no longer
-            // on any of the whitelists we need to clear the policy fixed flag
-            // as whitelisting trumps policy i.e. policy cannot grant a non
-            // grantable permission.
-            if ((oldFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
-                final boolean isGranted = permissionsState.hasPermission(permissionName, userId);
-                if (!isWhitelisted && isGranted) {
-                    mask |= PackageManager.FLAG_PERMISSION_POLICY_FIXED;
-                    newFlags &= ~PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+                int newFlags = oldFlags;
+                int mask = 0;
+                int whitelistFlagsCopy = whitelistFlags;
+                while (whitelistFlagsCopy != 0) {
+                    final int flag = 1 << Integer.numberOfTrailingZeros(whitelistFlagsCopy);
+                    whitelistFlagsCopy &= ~flag;
+                    switch (flag) {
+                        case FLAG_PERMISSION_WHITELIST_SYSTEM: {
+                            mask |= FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
+                            if (permissions != null && permissions.contains(permissionName)) {
+                                newFlags |= FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
+                            } else {
+                                newFlags &= ~FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
+                            }
+                        }
+                        break;
+                        case FLAG_PERMISSION_WHITELIST_UPGRADE: {
+                            mask |= FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
+                            if (permissions != null && permissions.contains(permissionName)) {
+                                newFlags |= FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
+                            } else {
+                                newFlags &= ~FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
+                            }
+                        }
+                        break;
+                        case FLAG_PERMISSION_WHITELIST_INSTALLER: {
+                            mask |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+                            if (permissions != null && permissions.contains(permissionName)) {
+                                newFlags |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+                            } else {
+                                newFlags &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+                            }
+                        }
+                        break;
+                    }
                 }
-            }
 
-            // If we are whitelisting an app that does not support runtime permissions
-            // we need to make sure it goes through the permission review UI at launch.
-            if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M
-                    && !wasWhitelisted && isWhitelisted) {
-                mask |= PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
-                newFlags |= PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
-            }
+                if (oldFlags == newFlags) {
+                    continue;
+                }
 
-            updatePermissionFlagsInternal(permissionName, pkg.getPackageName(), mask, newFlags,
-                    callingUid, userId, false, null /*callback*/);
+                updatePermissions = true;
+
+                final boolean wasWhitelisted = (oldFlags
+                        & (PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT)) != 0;
+                final boolean isWhitelisted = (newFlags
+                        & (PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT)) != 0;
+
+                // If the permission is policy fixed as granted but it is no longer
+                // on any of the whitelists we need to clear the policy fixed flag
+                // as whitelisting trumps policy i.e. policy cannot grant a non
+                // grantable permission.
+                if ((oldFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
+                    final boolean isGranted = permissionsState.hasPermission(permissionName,
+                            userId);
+                    if (!isWhitelisted && isGranted) {
+                        mask |= PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+                        newFlags &= ~PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+                    }
+                }
+
+                // If we are whitelisting an app that does not support runtime permissions
+                // we need to make sure it goes through the permission review UI at launch.
+                if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M
+                        && !wasWhitelisted && isWhitelisted) {
+                    mask |= PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+                    newFlags |= PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+                }
+
+                updatePermissionFlagsInternal(permissionName, pkg.getPackageName(), mask, newFlags,
+                        callingUid, userId, false, null /*callback*/);
+            }
         }
 
         if (updatePermissions) {
@@ -3861,15 +3877,22 @@
             restorePermissionState(pkg, false, pkg.getPackageName(), callback);
 
             // If this resulted in losing a permission we need to kill the app.
-            if (oldGrantedRestrictedPermissions != null) {
-                final int oldGrantedCount = oldGrantedRestrictedPermissions.size();
-                for (int i = 0; i < oldGrantedCount; i++) {
-                    final String permission = oldGrantedRestrictedPermissions.valueAt(i);
+            for (int i = 0; i < userIds.length; i++) {
+                int userId = userIds[i];
+                ArraySet<String> oldPermsForUser = oldGrantedRestrictedPermissions.get(userId);
+                if (oldPermsForUser == null) {
+                    continue;
+                }
+
+                final int oldGrantedCount = oldPermsForUser.size();
+                for (int j = 0; j < oldGrantedCount; j++) {
+                    final String permission = oldPermsForUser.valueAt(j);
                     // Sometimes we create a new permission state instance during update.
                     final PermissionsState newPermissionsState =
-                            PackageManagerServiceUtils.getPermissionsState(mPackageManagerInt, pkg);
+                            PackageManagerServiceUtils.getPermissionsState(mPackageManagerInt,
+                                    pkg);
                     if (!newPermissionsState.hasPermission(permission, userId)) {
-                        callback.onPermissionRevoked(pkg.getUid(), userId);
+                        callback.onPermissionRevoked(pkg.getUid(), userId, null);
                         break;
                     }
                 }
@@ -4228,7 +4251,7 @@
                         overridePolicy,
                         Process.SYSTEM_UID,
                         userId,
-                        callback);
+                        null, callback);
             } catch (IllegalArgumentException e) {
                 Slog.e(TAG,
                         "Failed to revoke "
@@ -4624,10 +4647,8 @@
         public void setWhitelistedRestrictedPermissions(@NonNull AndroidPackage pkg,
                 @NonNull int[] userIds, @Nullable List<String> permissions, int callingUid,
                 @PackageManager.PermissionWhitelistFlags int flags) {
-            for (int userId : userIds) {
-                setWhitelistedRestrictedPermissionsForUser(pkg, userId, permissions,
-                        callingUid, flags, mDefaultPermissionCallback);
-            }
+            setWhitelistedRestrictedPermissionsForUsers(pkg, userIds, permissions,
+                    callingUid, flags, mDefaultPermissionCallback);
         }
         @Override
         public void setWhitelistedRestrictedPermissions(String packageName,
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 4412162..2e83b23 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -156,7 +156,7 @@
         }
         public void onInstallPermissionGranted() {
         }
-        public void onPermissionRevoked(int uid, @UserIdInt int userId) {
+        public void onPermissionRevoked(int uid, @UserIdInt int userId, String reason) {
         }
         public void onInstallPermissionRevoked() {
         }
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 3ee5f50..37f088b 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -825,7 +825,15 @@
                 return;
             }
 
-            if (pkgInfo == null || pkg == null || pkgInfo.requestedPermissions == null) {
+            if (pkgInfo == null || pkg == null || pkgInfo.applicationInfo == null
+                    || pkgInfo.requestedPermissions == null) {
+                return;
+            }
+
+            final int uid = pkgInfo.applicationInfo.uid;
+            if (uid == Process.ROOT_UID || uid == Process.SYSTEM_UID) {
+                // Root and system server always pass permission checks, so don't touch their app
+                // ops to keep compatibility.
                 return;
             }
 
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/README.md b/services/core/java/com/android/server/soundtrigger_middleware/README.md
new file mode 100644
index 0000000..416548d
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/README.md
@@ -0,0 +1,19 @@
+# Sound Trigger Middleware
+TODO: Add component description.
+
+## Notes about thread synchronization
+This component has some tricky thread synchronization considerations due to its layered design and
+due to the fact that it is involved in both in-bound and out-bound calls from / to
+external components. To avoid potential deadlocks, a strict locking order must be ensured whenever
+nesting locks. The order is:
+- `SoundTriggerMiddlewareValidation` lock.
+- Audio policy service lock. This one is external - it should be assumed to be held whenever we're
+  inside the `ExternalCaptureStateTracker.setCaptureState()` call stack *AND* to be acquired from
+  within our calls into `AudioSessionProvider.acquireSession()`.
+- `SoundTriggerModule` lock.
+
+This dictates careful consideration of callbacks going from `SoundTriggerModule` to
+`SoundTriggerMiddlewareValidation` and especially those coming from the `setCaptureState()` path.
+We always invoke those calls outside of the `SoundTriggerModule` lock, so we can lock
+`SoundTriggerMiddlewareValidation`. However, in the `setCaptureState()` case, we have to use atomics
+in `SoundTriggerMiddlewareValidation` and avoid the lock.
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
index f4c77a0..5d25d2c 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
@@ -47,6 +47,9 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
@@ -328,7 +331,7 @@
         }
 
         /** Activity state. */
-        Activity activityState = Activity.LOADED;
+        private AtomicInteger mActivityState = new AtomicInteger(Activity.LOADED.ordinal());
 
         /** Human-readable description of the model. */
         final String description;
@@ -383,6 +386,14 @@
         void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
             parameterSupport.put(modelParam, range);
         }
+
+        Activity getActivityState() {
+            return Activity.values()[mActivityState.get()];
+        }
+
+        void setActivityState(Activity activity) {
+            mActivityState.set(activity.ordinal());
+        }
     }
 
     /**
@@ -393,7 +404,13 @@
             IBinder.DeathRecipient {
         private final ISoundTriggerCallback mCallback;
         private ISoundTriggerModule mDelegate;
-        private @NonNull Map<Integer, ModelState> mLoadedModels = new HashMap<>();
+        // While generally all the fields of this class must be changed under a lock, an exception
+        // is made for the specific case of changing a model state from ACTIVE to LOADED, which
+        // may happen as result of a recognition callback. This would happen atomically and is
+        // necessary in order to avoid deadlocks associated with locking from within callbacks
+        // possibly originating from the audio server.
+        private @NonNull
+        ConcurrentMap<Integer, ModelState> mLoadedModels = new ConcurrentHashMap<>();
         private final int mHandle;
         private ModuleStatus mState = ModuleStatus.ALIVE;
 
@@ -476,10 +493,9 @@
                 if (modelState == null) {
                     throw new IllegalStateException("Invalid handle: " + modelHandle);
                 }
-                if (modelState.activityState
-                        != ModelState.Activity.LOADED) {
+                if (modelState.getActivityState() != ModelState.Activity.LOADED) {
                     throw new IllegalStateException("Model with handle: " + modelHandle
-                            + " has invalid state for unloading: " + modelState.activityState);
+                            + " has invalid state for unloading: " + modelState.getActivityState());
                 }
 
                 // From here on, every exception isn't client's fault.
@@ -509,19 +525,21 @@
                 if (modelState == null) {
                     throw new IllegalStateException("Invalid handle: " + modelHandle);
                 }
-                if (modelState.activityState
-                        != ModelState.Activity.LOADED) {
+                if (modelState.getActivityState() != ModelState.Activity.LOADED) {
                     throw new IllegalStateException("Model with handle: " + modelHandle
                             + " has invalid state for starting recognition: "
-                            + modelState.activityState);
+                            + modelState.getActivityState());
                 }
 
                 // From here on, every exception isn't client's fault.
                 try {
+                    // Normally, we would set the state after the operation succeeds. However, since
+                    // the activity state may be reset outside of the lock, we set it here first,
+                    // and reset it in case of exception.
+                    modelState.setActivityState(ModelState.Activity.ACTIVE);
                     mDelegate.startRecognition(modelHandle, config);
-                    modelState.activityState =
-                            ModelState.Activity.ACTIVE;
                 } catch (Exception e) {
+                    modelState.setActivityState(ModelState.Activity.LOADED);
                     throw handleException(e);
                 }
             }
@@ -548,8 +566,7 @@
                 // From here on, every exception isn't client's fault.
                 try {
                     mDelegate.stopRecognition(modelHandle);
-                    modelState.activityState =
-                            ModelState.Activity.LOADED;
+                    modelState.setActivityState(ModelState.Activity.LOADED);
                 } catch (Exception e) {
                     throw handleException(e);
                 }
@@ -719,7 +736,7 @@
                 for (Map.Entry<Integer, ModelState> entry : mLoadedModels.entrySet()) {
                     pw.print(entry.getKey());
                     pw.print('\t');
-                    pw.print(entry.getValue().activityState.name());
+                    pw.print(entry.getValue().getActivityState().name());
                     pw.print('\t');
                     pw.print(entry.getValue().description);
                     pw.println();
@@ -735,48 +752,61 @@
 
         @Override
         public void onRecognition(int modelHandle, @NonNull RecognitionEvent event) {
-            synchronized (SoundTriggerMiddlewareValidation.this) {
-                if (event.status != RecognitionStatus.FORCED) {
-                    mLoadedModels.get(modelHandle).activityState =
-                            ModelState.Activity.LOADED;
+            // We cannot obtain a lock on SoundTriggerMiddlewareValidation.this, since this call
+            // might be coming from the audio server (via setCaptureState()) while it is holding
+            // a lock that is also acquired while loading / unloading models. Thus, we require a
+            // strict locking order here, where obtaining our lock must always come first.
+            // To avoid this problem, we use an atomic model activity state. There is a risk of the
+            // model not being in the mLoadedModels map here, since it might have been stopped /
+            // unloaded while the event was in flight.
+            if (event.status != RecognitionStatus.FORCED) {
+                ModelState modelState = mLoadedModels.get(modelHandle);
+                if (modelState != null) {
+                    modelState.setActivityState(ModelState.Activity.LOADED);
                 }
-                try {
-                    mCallback.onRecognition(modelHandle, event);
-                } catch (RemoteException e) {
-                    // Dead client will be handled by binderDied() - no need to handle here.
-                    // In any case, client callbacks are considered best effort.
-                    Log.e(TAG, "Client callback exception.", e);
-                }
+            }
+            try {
+                mCallback.onRecognition(modelHandle, event);
+            } catch (RemoteException e) {
+                // Dead client will be handled by binderDied() - no need to handle here.
+                // In any case, client callbacks are considered best effort.
+                Log.e(TAG, "Client callback exception.", e);
             }
         }
 
         @Override
         public void onPhraseRecognition(int modelHandle, @NonNull PhraseRecognitionEvent event) {
-            synchronized (SoundTriggerMiddlewareValidation.this) {
-                if (event.common.status != RecognitionStatus.FORCED) {
-                    mLoadedModels.get(modelHandle).activityState =
-                            ModelState.Activity.LOADED;
+            // We cannot obtain a lock on SoundTriggerMiddlewareValidation.this, since this call
+            // might be coming from the audio server (via setCaptureState()) while it is holding
+            // a lock that is also acquired while loading / unloading models. Thus, we require a
+            // strict locking order here, where obtaining our lock must always come first.
+            // To avoid this problem, we use an atomic model activity state. There is a risk of the
+            // model not being in the mLoadedModels map here, since it might have been stopped /
+            // unloaded while the event was in flight.
+            if (event.common.status != RecognitionStatus.FORCED) {
+                ModelState modelState = mLoadedModels.get(modelHandle);
+                if (modelState != null) {
+                    modelState.setActivityState(ModelState.Activity.LOADED);
                 }
-                try {
-                    mCallback.onPhraseRecognition(modelHandle, event);
-                } catch (RemoteException e) {
-                    // Dead client will be handled by binderDied() - no need to handle here.
-                    // In any case, client callbacks are considered best effort.
-                    Log.e(TAG, "Client callback exception.", e);
-                }
+            }
+            try {
+                mCallback.onPhraseRecognition(modelHandle, event);
+            } catch (RemoteException e) {
+                // Dead client will be handled by binderDied() - no need to handle here.
+                // In any case, client callbacks are considered best effort.
+                Log.e(TAG, "Client callback exception.", e);
             }
         }
 
         @Override
         public void onRecognitionAvailabilityChange(boolean available) {
-            synchronized (SoundTriggerMiddlewareValidation.this) {
-                try {
-                    mCallback.onRecognitionAvailabilityChange(available);
-                } catch (RemoteException e) {
-                    // Dead client will be handled by binderDied() - no need to handle here.
-                    // In any case, client callbacks are considered best effort.
-                    Log.e(TAG, "Client callback exception.", e);
-                }
+            // Not locking to avoid deadlocks (not affecting any state).
+            try {
+                mCallback.onRecognitionAvailabilityChange(available);
+            } catch (RemoteException e) {
+                // Dead client will be handled by binderDied() - no need to handle here.
+                // In any case, client callbacks are considered best effort.
+                Log.e(TAG, "Client callback exception.", e);
             }
         }
 
@@ -804,10 +834,9 @@
                     // Gracefully stop all active recognitions and unload the models.
                     for (Map.Entry<Integer, ModelState> entry :
                             mLoadedModels.entrySet()) {
-                        if (entry.getValue().activityState
-                                == ModelState.Activity.ACTIVE) {
-                            mDelegate.stopRecognition(entry.getKey());
-                        }
+                        // Idempotent call, no harm in calling even for models that are already
+                        // stopped.
+                        mDelegate.stopRecognition(entry.getKey());
                         mDelegate.unloadModel(entry.getKey());
                     }
                     // Detach.
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 522e5e1..f809ed4 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -42,6 +42,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -153,18 +154,28 @@
      *
      * @param active true iff external capture is active.
      */
-    synchronized void setExternalCaptureState(boolean active) {
-        if (mProperties.concurrentCapture) {
-            // If we support concurrent capture, we don't care about any of this.
-            return;
-        }
-        mRecognitionAvailable = !active;
-        if (!mRecognitionAvailable) {
-            // Our module does not support recognition while a capture is active -
-            // need to abort all active recognitions.
-            for (Session session : mActiveSessions) {
-                session.abortActiveRecognitions();
+    void setExternalCaptureState(boolean active) {
+        // We should never invoke callbacks while holding the lock, since this may deadlock with
+        // forward calls. Thus, we first gather all the callbacks we need to invoke while holding
+        // the lock, but invoke them after releasing it.
+        List<Runnable> callbacks = new LinkedList<>();
+
+        synchronized (this) {
+            if (mProperties.concurrentCapture) {
+                // If we support concurrent capture, we don't care about any of this.
+                return;
             }
+            mRecognitionAvailable = !active;
+            if (!mRecognitionAvailable) {
+                // Our module does not support recognition while a capture is active -
+                // need to abort all active recognitions.
+                for (Session session : mActiveSessions) {
+                    session.abortActiveRecognitions(callbacks);
+                }
+            }
+        }
+        for (Runnable callback : callbacks) {
+            callback.run();
         }
         for (Session session : mActiveSessions) {
             session.notifyRecognitionAvailability();
@@ -329,9 +340,18 @@
 
         @Override
         public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
+            // We should never invoke callbacks while holding the lock, since this may deadlock with
+            // forward calls. Thus, we first gather all the callbacks we need to invoke while holding
+            // the lock, but invoke them after releasing it.
+            List<Runnable> callbacks = new LinkedList<>();
+
             synchronized (SoundTriggerModule.this) {
                 checkValid();
-                mLoadedModels.get(modelHandle).startRecognition(config);
+                mLoadedModels.get(modelHandle).startRecognition(config, callbacks);
+            }
+
+            for (Runnable callback : callbacks) {
+                callback.run();
             }
         }
 
@@ -377,10 +397,12 @@
 
         /**
          * Abort all currently active recognitions.
+         * @param callbacks Will be appended with a list of callbacks that need to be invoked
+         *                  after this method returns, without holding the module lock.
          */
-        private void abortActiveRecognitions() {
+        private void abortActiveRecognitions(@NonNull List<Runnable> callbacks) {
             for (Model model : mLoadedModels.values()) {
-                model.abortActiveRecognition();
+                model.abortActiveRecognition(callbacks);
             }
         }
 
@@ -475,10 +497,11 @@
                 return mSession.mSessionHandle;
             }
 
-            private void startRecognition(@NonNull RecognitionConfig config) {
+            private void startRecognition(@NonNull RecognitionConfig config,
+                    @NonNull List<Runnable> callbacks) {
                 if (!mRecognitionAvailable) {
                     // Recognition is unavailable - send an abort event immediately.
-                    notifyAbort();
+                    callbacks.add(this::notifyAbort);
                     return;
                 }
                 android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig =
@@ -525,8 +548,12 @@
                                 ConversionUtil.aidl2hidlModelParameter(modelParam)));
             }
 
-            /** Abort the recognition, if active. */
-            private void abortActiveRecognition() {
+            /**
+             * Abort the recognition, if active.
+             * @param callbacks Will be appended with a list of callbacks that need to be invoked
+             *                  after this method returns, without holding the module lock.
+             */
+            private void abortActiveRecognition(List<Runnable> callbacks) {
                 // If we're inactive, do nothing.
                 if (getState() != ModelState.ACTIVE) {
                     return;
@@ -535,7 +562,7 @@
                 stopRecognition();
 
                 // Notify the client that recognition has been aborted.
-                notifyAbort();
+                callbacks.add(this::notifyAbort);
             }
 
             /** Notify the client that recognition has been aborted. */
@@ -577,42 +604,44 @@
             public void recognitionCallback(
                     @NonNull ISoundTriggerHwCallback.RecognitionEvent recognitionEvent,
                     int cookie) {
+                RecognitionEvent aidlEvent =
+                        ConversionUtil.hidl2aidlRecognitionEvent(recognitionEvent);
+                aidlEvent.captureSession = mSession.mSessionHandle;
                 synchronized (SoundTriggerModule.this) {
-                    RecognitionEvent aidlEvent =
-                            ConversionUtil.hidl2aidlRecognitionEvent(recognitionEvent);
-                    aidlEvent.captureSession = mSession.mSessionHandle;
-                    try {
-                        mCallback.onRecognition(mHandle, aidlEvent);
-                    } catch (RemoteException e) {
-                        // Dead client will be handled by binderDied() - no need to handle here.
-                        // In any case, client callbacks are considered best effort.
-                        Log.e(TAG, "Client callback execption.", e);
-                    }
                     if (aidlEvent.status != RecognitionStatus.FORCED) {
                         setState(ModelState.LOADED);
                     }
                 }
+                // The callback must be invoked outside of the lock.
+                try {
+                    mCallback.onRecognition(mHandle, aidlEvent);
+                } catch (RemoteException e) {
+                    // We're not expecting any exceptions here.
+                    throw e.rethrowAsRuntimeException();
+                }
             }
 
             @Override
             public void phraseRecognitionCallback(
                     @NonNull ISoundTriggerHwCallback.PhraseRecognitionEvent phraseRecognitionEvent,
                     int cookie) {
+                PhraseRecognitionEvent aidlEvent =
+                        ConversionUtil.hidl2aidlPhraseRecognitionEvent(phraseRecognitionEvent);
+                aidlEvent.common.captureSession = mSession.mSessionHandle;
+
                 synchronized (SoundTriggerModule.this) {
-                    PhraseRecognitionEvent aidlEvent =
-                            ConversionUtil.hidl2aidlPhraseRecognitionEvent(phraseRecognitionEvent);
-                    aidlEvent.common.captureSession = mSession.mSessionHandle;
-                    try {
-                        mCallback.onPhraseRecognition(mHandle, aidlEvent);
-                    } catch (RemoteException e) {
-                        // Dead client will be handled by binderDied() - no need to handle here.
-                        // In any case, client callbacks are considered best effort.
-                        Log.e(TAG, "Client callback execption.", e);
-                    }
                     if (aidlEvent.common.status != RecognitionStatus.FORCED) {
                         setState(ModelState.LOADED);
                     }
                 }
+
+                // The callback must be invoked outside of the lock.
+                try {
+                    mCallback.onPhraseRecognition(mHandle, aidlEvent);
+                } catch (RemoteException e) {
+                    // We're not expecting any exceptions here.
+                    throw e.rethrowAsRuntimeException();
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 09fd33d..dbdef23 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -2575,11 +2575,17 @@
                     lastHighWaterMark, section, true, statsFiles, procStats);
             procStats.dumpAggregatedProtoForStatsd(protoStreams, MAX_PROCSTATS_RAW_SHARD_SIZE);
 
-            for (ProtoOutputStream proto : protoStreams) {
-                if (proto.getBytes().length > 0) {
+            for (int i = 0; i < protoStreams.length; i++) {
+                byte[] bytes = protoStreams[i].getBytes(); // cache the value
+                if (bytes.length > 0) {
                     StatsEvent e = StatsEvent.newBuilder()
                             .setAtomId(atomTag)
-                            .writeByteArray(proto.getBytes())
+                            .writeByteArray(bytes)
+                            // This is a shard ID, and is specified in the metric definition to be
+                            // a dimension. This will result in statsd using RANDOM_ONE_SAMPLE to
+                            // keep all the shards, as it thinks each shard is a different dimension
+                            // of data.
+                            .writeInt(i)
                             .build();
                     pulledData.add(e);
                 }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 4ba58bd..39e839d 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -52,6 +52,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.statusbar.IStatusBar;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
@@ -61,6 +62,7 @@
 import com.android.internal.view.AppearanceRegion;
 import com.android.server.LocalServices;
 import com.android.server.UiThread;
+import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.notification.NotificationDelegate;
 import com.android.server.policy.GlobalActionsProvider;
 import com.android.server.power.ShutdownThread;
@@ -1402,6 +1404,17 @@
     }
 
     @Override
+    public void hideCurrentInputMethodForBubbles() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            InputMethodManagerInternal.get().hideCurrentInputMethod(
+                    SoftInputShowHideReason.HIDE_BUBBLES);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
     public void grantInlineReplyUriPermission(String key, Uri uri, UserHandle user,
             String packageName) {
         enforceStatusBarService();
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index c38d649..5f63233 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -115,7 +115,7 @@
     private static final String TAG = "UriGrantsManagerService";
     // Maximum number of persisted Uri grants a package is allowed
     private static final int MAX_PERSISTED_URI_GRANTS = 128;
-    private static final boolean ENABLE_DYNAMIC_PERMISSIONS = false;
+    private static final boolean ENABLE_DYNAMIC_PERMISSIONS = true;
 
     private final Object mLock = new Object();
     private final H mH;
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 6fbfa68..16ca60d 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -52,6 +52,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.am.PendingIntentRecord;
+import com.android.server.uri.NeededUriGrants;
 import com.android.server.wm.ActivityStackSupervisor.PendingActivityLaunch;
 import com.android.server.wm.ActivityStarter.DefaultFactory;
 import com.android.server.wm.ActivityStarter.Factory;
@@ -402,6 +403,7 @@
             // potentially acquire activity manager lock that leads to deadlock.
             for (int i = 0; i < intents.length; i++) {
                 Intent intent = intents[i];
+                NeededUriGrants intentGrants = null;
 
                 // Refuse possible leaked file descriptors.
                 if (intent.hasFileDescriptors()) {
@@ -418,6 +420,14 @@
                         0 /* startFlags */, null /* profilerInfo */, userId, filterCallingUid);
                 aInfo = mService.mAmInternal.getActivityInfoForUser(aInfo, userId);
 
+                // Carefully collect grants without holding lock
+                if (aInfo != null) {
+                    intentGrants = mSupervisor.mService.mUgmInternal
+                            .checkGrantUriPermissionFromIntent(intent, filterCallingUid,
+                                    aInfo.applicationInfo.packageName,
+                                    UserHandle.getUserId(aInfo.applicationInfo.uid));
+                }
+
                 if (aInfo != null) {
                     if ((aInfo.applicationInfo.privateFlags
                             & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
@@ -433,6 +443,7 @@
                         ? options
                         : null;
                 starters[i] = obtainStarter(intent, reason)
+                        .setIntentGrants(intentGrants)
                         .setCaller(caller)
                         .setResolvedType(resolvedTypes[i])
                         .setActivityInfo(aInfo)
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index daa97b5..25842f5 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -639,8 +639,14 @@
                         mRequest.intent, caller);
             }
 
-            // Do not lock the resolving to avoid potential deadlock.
+            // If the caller hasn't already resolved the activity, we're willing
+            // to do so here, but because that may require acquiring the AM lock
+            // as part of calculating the NeededUriGrants, we must never hold
+            // the WM lock here to avoid deadlocking.
             if (mRequest.activityInfo == null) {
+                if (Thread.holdsLock(mService.mGlobalLock)) {
+                    Slog.wtf(TAG, new IllegalStateException("Caller must not hold WM lock"));
+                }
                 mRequest.resolveActivity(mSupervisor);
             }
 
@@ -2632,6 +2638,11 @@
         return mRequest.intent;
     }
 
+    ActivityStarter setIntentGrants(NeededUriGrants intentGrants) {
+        mRequest.intentGrants = intentGrants;
+        return this;
+    }
+
     ActivityStarter setReason(String reason) {
         mRequest.reason = reason;
         return this;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index cf453c7..205523b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6174,12 +6174,10 @@
                 boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent,
                 boolean allowBackgroundActivityStart) {
             assertPackageMatchesCallingUid(callingPackage);
-            synchronized (mGlobalLock) {
-                return getActivityStartController().startActivitiesInPackage(uid, realCallingPid,
-                        realCallingUid, callingPackage, callingFeatureId, intents, resolvedTypes,
-                        resultTo, options, userId, validateIncomingUser, originatingPendingIntent,
-                        allowBackgroundActivityStart);
-            }
+            return getActivityStartController().startActivitiesInPackage(uid, realCallingPid,
+                    realCallingUid, callingPackage, callingFeatureId, intents, resolvedTypes,
+                    resultTo, options, userId, validateIncomingUser, originatingPendingIntent,
+                    allowBackgroundActivityStart);
         }
 
         @Override
@@ -6190,13 +6188,11 @@
                 boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent,
                 boolean allowBackgroundActivityStart) {
             assertPackageMatchesCallingUid(callingPackage);
-            synchronized (mGlobalLock) {
-                return getActivityStartController().startActivityInPackage(uid, realCallingPid,
-                        realCallingUid, callingPackage, callingFeatureId, intent, resolvedType,
-                        resultTo, resultWho, requestCode, startFlags, options, userId, inTask,
-                        reason, validateIncomingUser, originatingPendingIntent,
-                        allowBackgroundActivityStart);
-            }
+            return getActivityStartController().startActivityInPackage(uid, realCallingPid,
+                    realCallingUid, callingPackage, callingFeatureId, intent, resolvedType,
+                    resultTo, resultWho, requestCode, startFlags, options, userId, inTask,
+                    reason, validateIncomingUser, originatingPendingIntent,
+                    allowBackgroundActivityStart);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index 8fa8119..dd1d55b 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -83,7 +83,8 @@
                 if (task == null) {
                     throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
                 }
-                return mService.getRecentTasks().createRecentTaskInfo(task);
+                return mService.getRecentTasks().createRecentTaskInfo(task,
+                        false /* stripExtras */);
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 9b5d94e..afe40b8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1185,6 +1185,9 @@
         }
 
         activity.onRemovedFromDisplay();
+        if (activity == mFixedRotationLaunchingApp) {
+            setFixedRotationLaunchingAppUnchecked(null);
+        }
     }
 
     @Override
@@ -1468,6 +1471,12 @@
             // It has been set and not yet finished.
             return true;
         }
+        if (!r.occludesParent() || r.isVisible()) {
+            // While entering or leaving a translucent or floating activity (e.g. dialog style),
+            // there is a visible activity in the background. Then it still needs rotation animation
+            // to cover the activity configuration change.
+            return false;
+        }
         if (checkOpening) {
             if (!mAppTransition.isTransitionSet() || !mOpeningApps.contains(r)) {
                 // Apply normal rotation animation in case of the activity set different requested
@@ -5636,6 +5645,12 @@
          */
         void onStartRecentsAnimation(@NonNull ActivityRecord r) {
             mAnimatingRecents = r;
+            if (r.isVisible() && mFocusedApp != null && !mFocusedApp.occludesParent()) {
+                // The recents activity has shown with the orientation determined by the top
+                // activity, keep its current orientation to avoid flicking by the configuration
+                // change of visible activity.
+                return;
+            }
             rotateInDifferentOrientationIfNeeded(r);
             if (r.hasFixedRotationTransform()) {
                 // Set the record so we can recognize it to continue to update display orientation
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 24bb7c8..1b58fc1 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -961,7 +961,7 @@
                 continue;
             }
 
-            res.add(createRecentTaskInfo(task));
+            res.add(createRecentTaskInfo(task, true /* stripExtras */));
         }
         return res;
     }
@@ -1832,9 +1832,9 @@
     /**
      * Creates a new RecentTaskInfo from a Task.
      */
-    ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr) {
+    ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras) {
         ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
-        tr.fillTaskInfo(rti);
+        tr.fillTaskInfo(rti, stripExtras);
         // Fill in some deprecated values
         rti.id = rti.isRunning ? rti.taskId : INVALID_TASK_ID;
         rti.persistentId = rti.taskId;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index fec4849..a7b1209 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2934,7 +2934,7 @@
             final Task rootTask = getRootTask();
             final Task topNonOrgTask =
                     rootTask.mCreatedByOrganizer ? rootTask.getTopMostTask() : rootTask;
-            if (isDescendantOf(topNonOrgTask)) {
+            if (this == topNonOrgTask || isDescendantOf(topNonOrgTask)) {
                 return false;
             }
         }
@@ -3551,11 +3551,19 @@
         }
     }
 
+
     /**
      * Fills in a {@link TaskInfo} with information from this task. Note that the base intent in the
      * task info will not include any extras or clip data.
      */
     void fillTaskInfo(TaskInfo info) {
+        fillTaskInfo(info, true /* stripExtras */);
+    }
+
+    /**
+     * Fills in a {@link TaskInfo} with information from this task.
+     */
+    void fillTaskInfo(TaskInfo info, boolean stripExtras) {
         getNumRunningActivities(mReuseActivitiesReport);
         info.userId = mUserId;
         info.stackId = getRootTaskId();
@@ -3566,7 +3574,9 @@
         // Make a copy of base intent because this is like a snapshot info.
         // Besides, {@link RecentTasks#getRecentTasksImpl} may modify it.
         final int baseIntentFlags = baseIntent == null ? 0 : baseIntent.getFlags();
-        info.baseIntent = baseIntent == null ? new Intent() : baseIntent.cloneFilter();
+        info.baseIntent = baseIntent == null
+                ? new Intent()
+                : stripExtras ? baseIntent.cloneFilter() : new Intent(baseIntent);
         info.baseIntent.setFlags(baseIntentFlags);
         info.baseActivity = mReuseActivitiesReport.base != null
                 ? mReuseActivitiesReport.base.intent.getComponent()
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 52fb941..564eecf 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5142,8 +5142,8 @@
                 }
                 case WINDOW_STATE_BLAST_SYNC_TIMEOUT: {
                     synchronized (mGlobalLock) {
-                      final WindowState ws = (WindowState) msg.obj;
-                      ws.finishDrawing(null);
+                        final WindowState ws = (WindowState) msg.obj;
+                        ws.immediatelyNotifyBlastSync();
                     }
                     break;
                 }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index fbc5afa..46e1bf0 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -428,6 +428,9 @@
         try {
             callback.onTransactionReady(mSyncId, mergedTransaction);
         } catch (RemoteException e) {
+            // If there's an exception when trying to send the mergedTransaction to the client, we
+            // should immediately apply it here so the transactions aren't lost.
+            mergedTransaction.apply();
         }
 
         mTransactionCallbacksByPendingSyncId.remove(mSyncId);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 26a1fea..49ef4e4 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2192,7 +2192,7 @@
     void removeIfPossible() {
         super.removeIfPossible();
         removeIfPossible(false /*keepVisibleDeadWindow*/);
-        finishDrawing(null);
+        immediatelyNotifyBlastSync();
     }
 
     private void removeIfPossible(boolean keepVisibleDeadWindow) {
@@ -5806,7 +5806,7 @@
         // client will not render when visibility is GONE. Therefore, call finishDrawing here to
         // prevent system server from blocking on a window that will not draw.
         if (viewVisibility == View.GONE && mUsingBLASTSyncTransaction) {
-            finishDrawing(null);
+            immediatelyNotifyBlastSync();
         }
     }
 
@@ -5844,7 +5844,6 @@
             return mWinAnimator.finishDrawingLocked(postDrawTransaction);
         }
 
-        mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
         if (postDrawTransaction != null) {
             mBLASTSyncTransaction.merge(postDrawTransaction);
         }
@@ -5853,8 +5852,9 @@
         return mWinAnimator.finishDrawingLocked(null);
     }
 
-    @VisibleForTesting
-    void notifyBlastSyncTransaction() {
+    private void notifyBlastSyncTransaction() {
+        mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
+
         if (!mNotifyBlastOnSurfacePlacement || mWaitingListener == null) {
             mNotifyBlastOnSurfacePlacement = false;
             return;
@@ -5877,6 +5877,11 @@
         mNotifyBlastOnSurfacePlacement = false;
     }
 
+    void immediatelyNotifyBlastSync() {
+        finishDrawing(null);
+        notifyBlastSyncTransaction();
+    }
+
     private boolean requestResizeForBlastSync() {
         return useBLASTSync() && !mResizeForBlastSyncReported;
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
index e3453a0..b2847ce 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
@@ -81,8 +82,10 @@
         doReturn(mIPackageManager).when(() -> AppGlobals.getPackageManager());
         when(mIPackageManager.getPackageUid(eq(TEST_PACKAGE_NAME), anyInt(), anyInt())).thenReturn(
                 TEST_CALLING_UID);
+        ActivityManagerConstants constants = mock(ActivityManagerConstants.class);
+        constants.PENDINGINTENT_WARNING_THRESHOLD = 2000;
         mPendingIntentController = new PendingIntentController(Looper.getMainLooper(),
-                mUserController);
+                mUserController, constants);
         mPendingIntentController.onActivityManagerInternalAdded();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FullScreenMagnificationGestureHandlerTest.java
index 2007d4f..1cbee12 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/FullScreenMagnificationGestureHandlerTest.java
@@ -20,6 +20,7 @@
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
 import static android.view.MotionEvent.ACTION_POINTER_UP;
+import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.server.testutils.TestUtils.strictMock;
 
@@ -38,11 +39,13 @@
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.content.Context;
+import android.graphics.PointF;
 import android.os.Handler;
 import android.os.Message;
 import android.util.DebugUtils;
 import android.view.InputDevice;
 import android.view.MotionEvent;
+import android.view.ViewConfiguration;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -56,6 +59,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+
+import java.util.ArrayList;
+import java.util.List;
 import java.util.function.IntConsumer;
 
 /**
@@ -106,6 +112,7 @@
     // Co-prime x and y, to potentially catch x-y-swapped errors
     public static final float DEFAULT_X = 301;
     public static final float DEFAULT_Y = 299;
+    public static final PointF DEFAULT_POINT = new PointF(DEFAULT_X, DEFAULT_Y);
 
     private static final int DISPLAY_0 = 0;
 
@@ -327,6 +334,107 @@
         });
     }
 
+    @Test
+    public void testTwoFingersOneTap_zoomedState_dispatchMotionEvents() {
+        goFromStateIdleTo(STATE_ZOOMED);
+        final EventCaptor eventCaptor = new EventCaptor();
+        mMgh.setNext(eventCaptor);
+
+        send(downEvent());
+        send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y));
+        send(pointerEvent(ACTION_POINTER_UP, DEFAULT_X * 2, DEFAULT_Y));
+        send(upEvent());
+
+        assertIn(STATE_ZOOMED);
+        final List<Integer> expectedActions = new ArrayList();
+        expectedActions.add(Integer.valueOf(ACTION_DOWN));
+        expectedActions.add(Integer.valueOf(ACTION_POINTER_DOWN));
+        expectedActions.add(Integer.valueOf(ACTION_POINTER_UP));
+        expectedActions.add(Integer.valueOf(ACTION_UP));
+        assertActionsInOrder(eventCaptor.mEvents, expectedActions);
+
+        returnToNormalFrom(STATE_ZOOMED);
+    }
+
+    @Test
+    public void testThreeFingersOneTap_zoomedState_dispatchMotionEvents() {
+        goFromStateIdleTo(STATE_ZOOMED);
+        final EventCaptor eventCaptor = new EventCaptor();
+        mMgh.setNext(eventCaptor);
+        PointF pointer1 = DEFAULT_POINT;
+        PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+        PointF pointer3 = new PointF(DEFAULT_X * 2, DEFAULT_Y);
+
+        send(downEvent());
+        send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}));
+        send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2, pointer3}));
+        send(pointerEvent(ACTION_POINTER_UP, new PointF[] {pointer1, pointer2, pointer3}));
+        send(pointerEvent(ACTION_POINTER_UP, new PointF[] {pointer1, pointer2, pointer3}));
+        send(upEvent());
+
+        assertIn(STATE_ZOOMED);
+        final List<Integer> expectedActions = new ArrayList();
+        expectedActions.add(Integer.valueOf(ACTION_DOWN));
+        expectedActions.add(Integer.valueOf(ACTION_POINTER_DOWN));
+        expectedActions.add(Integer.valueOf(ACTION_POINTER_DOWN));
+        expectedActions.add(Integer.valueOf(ACTION_POINTER_UP));
+        expectedActions.add(Integer.valueOf(ACTION_POINTER_UP));
+        expectedActions.add(Integer.valueOf(ACTION_UP));
+        assertActionsInOrder(eventCaptor.mEvents, expectedActions);
+
+        returnToNormalFrom(STATE_ZOOMED);
+    }
+
+    @Test
+    public void testFirstFingerSwipe_TwoPinterDownAndZoomedState_panningState() {
+        goFromStateIdleTo(STATE_ZOOMED);
+        PointF pointer1 = DEFAULT_POINT;
+        PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+
+        send(downEvent());
+        send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}));
+        //The minimum movement to transit to panningState.
+        final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
+        pointer1.offset(sWipeMinDistance + 1, 0);
+        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}));
+        assertIn(STATE_PANNING);
+
+        assertIn(STATE_PANNING);
+        returnToNormalFrom(STATE_PANNING);
+    }
+
+    @Test
+    public void testSecondFingerSwipe_TwoPinterDownAndZoomedState_panningState() {
+        goFromStateIdleTo(STATE_ZOOMED);
+        PointF pointer1 = DEFAULT_POINT;
+        PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+
+        send(downEvent());
+        send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}));
+        //The minimum movement to transit to panningState.
+        final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
+        pointer2.offset(sWipeMinDistance + 1, 0);
+        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}));
+        assertIn(STATE_PANNING);
+
+        assertIn(STATE_PANNING);
+        returnToNormalFrom(STATE_PANNING);
+    }
+
+    private void assertActionsInOrder(List<MotionEvent> actualEvents,
+            List<Integer> expectedActions) {
+        assertTrue(actualEvents.size() == expectedActions.size());
+        final int size = actualEvents.size();
+        for (int i = 0; i < size; i++) {
+            final int expectedAction = expectedActions.get(i);
+            final int actualAction = actualEvents.get(i).getActionMasked();
+            assertTrue(String.format(
+                    "%dth action %s is not matched, actual events : %s, ", i,
+                    MotionEvent.actionToString(expectedAction), actualEvents),
+                    actualAction == expectedAction);
+        }
+    }
+
     private void assertZoomsImmediatelyOnSwipeFrom(int state) {
         goFromStateIdleTo(state);
         swipeAndHold();
@@ -467,6 +575,7 @@
                     goFromStateIdleTo(STATE_ZOOMED);
                     send(downEvent());
                     send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y));
+                    fastForward(ViewConfiguration.getTapTimeout());
                 } break;
                 case STATE_SCALING_AND_PANNING: {
                     goFromStateIdleTo(STATE_PANNING);
@@ -619,40 +728,67 @@
                 MotionEvent.ACTION_UP, x, y, 0));
     }
 
-    private MotionEvent pointerEvent(int action, float x, float y) {
-        MotionEvent.PointerProperties defPointerProperties = new MotionEvent.PointerProperties();
-        defPointerProperties.id = 0;
-        defPointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
-        MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties();
-        pointerProperties.id = 1;
-        pointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
 
-        MotionEvent.PointerCoords defPointerCoords = new MotionEvent.PointerCoords();
-        defPointerCoords.x = DEFAULT_X;
-        defPointerCoords.y = DEFAULT_Y;
-        MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
-        pointerCoords.x = x;
-        pointerCoords.y = y;
+    private MotionEvent pointerEvent(int action, float x, float y) {
+        return pointerEvent(action, new PointF[] {DEFAULT_POINT, new PointF(x, y)});
+    }
+
+    private MotionEvent pointerEvent(int action, PointF[] pointersPosition) {
+        final MotionEvent.PointerProperties[] PointerPropertiesArray =
+                new MotionEvent.PointerProperties[pointersPosition.length];
+        for (int i = 0; i < pointersPosition.length; i++) {
+            MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties();
+            pointerProperties.id = i;
+            pointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
+            PointerPropertiesArray[i] = pointerProperties;
+        }
+
+        final MotionEvent.PointerCoords[] pointerCoordsArray =
+                new MotionEvent.PointerCoords[pointersPosition.length];
+        for (int i = 0; i < pointersPosition.length; i++) {
+            MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
+            pointerCoords.x = pointersPosition[i].x;
+            pointerCoords.y = pointersPosition[i].y;
+            pointerCoordsArray[i] = pointerCoords;
+        }
 
         return MotionEvent.obtain(
-            /* downTime */ mClock.now(),
-            /* eventTime */ mClock.now(),
-            /* action */ action,
-            /* pointerCount */ 2,
-            /* pointerProperties */ new MotionEvent.PointerProperties[] {
-                    defPointerProperties, pointerProperties},
-            /* pointerCoords */ new MotionEvent.PointerCoords[] { defPointerCoords, pointerCoords },
-            /* metaState */ 0,
-            /* buttonState */ 0,
-            /* xPrecision */ 1.0f,
-            /* yPrecision */ 1.0f,
-            /* deviceId */ 0,
-            /* edgeFlags */ 0,
-            /* source */ InputDevice.SOURCE_TOUCHSCREEN,
-            /* flags */ 0);
+                /* downTime */ mClock.now(),
+                /* eventTime */ mClock.now(),
+                /* action */ action,
+                /* pointerCount */ pointersPosition.length,
+                /* pointerProperties */ PointerPropertiesArray,
+                /* pointerCoords */ pointerCoordsArray,
+                /* metaState */ 0,
+                /* buttonState */ 0,
+                /* xPrecision */ 1.0f,
+                /* yPrecision */ 1.0f,
+                /* deviceId */ 0,
+                /* edgeFlags */ 0,
+                /* source */ InputDevice.SOURCE_TOUCHSCREEN,
+                /* flags */ 0);
     }
 
+
     private String stateDump() {
         return "\nCurrent state dump:\n" + mMgh + "\n" + mHandler.getPendingMessages();
     }
+
+    private class EventCaptor implements EventStreamTransformation {
+        List<MotionEvent> mEvents = new ArrayList<>();
+
+        @Override
+        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+            mEvents.add(event.copy());
+        }
+
+        @Override
+        public void setNext(EventStreamTransformation next) {
+        }
+
+        @Override
+        public EventStreamTransformation getNext() {
+            return null;
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
index 8607ec6..7538468 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
@@ -17,7 +17,6 @@
 
 import android.hardware.hdmi.HdmiPortInfo;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
-import android.os.MessageQueue;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.hdmi.HdmiCecController.NativeWrapper;
@@ -53,13 +52,16 @@
     private HdmiPortInfo[] mHdmiPortInfo = null;
 
     @Override
-    public long nativeInit(HdmiCecController handler, MessageQueue messageQueue) {
-        return 1L;
+    public String nativeInit() {
+        return "[class or subclass of IHdmiCec]@Proxy";
     }
 
     @Override
+    public void setCallback(HdmiCecController.HdmiCecCallback callback) {}
+
+    @Override
     public int nativeSendCecCommand(
-            long controllerPtr, int srcAddress, int dstAddress, byte[] body) {
+            int srcAddress, int dstAddress, byte[] body) {
         if (body.length == 0) {
             return mPollAddressResponse[dstAddress];
         } else {
@@ -69,30 +71,30 @@
     }
 
     @Override
-    public int nativeAddLogicalAddress(long controllerPtr, int logicalAddress) {
+    public int nativeAddLogicalAddress(int logicalAddress) {
         return 0;
     }
 
     @Override
-    public void nativeClearLogicalAddress(long controllerPtr) {}
+    public void nativeClearLogicalAddress() {}
 
     @Override
-    public int nativeGetPhysicalAddress(long controllerPtr) {
+    public int nativeGetPhysicalAddress() {
         return mMyPhysicalAddress;
     }
 
     @Override
-    public int nativeGetVersion(long controllerPtr) {
+    public int nativeGetVersion() {
         return 0;
     }
 
     @Override
-    public int nativeGetVendorId(long controllerPtr) {
+    public int nativeGetVendorId() {
         return 0;
     }
 
     @Override
-    public HdmiPortInfo[] nativeGetPortInfos(long controllerPtr) {
+    public HdmiPortInfo[] nativeGetPortInfos() {
         if (mHdmiPortInfo == null) {
             mHdmiPortInfo = new HdmiPortInfo[1];
             mHdmiPortInfo[0] = new HdmiPortInfo(1, 1, 0x1000, true, true, true);
@@ -101,16 +103,16 @@
     }
 
     @Override
-    public void nativeSetOption(long controllerPtr, int flag, boolean enabled) {}
+    public void nativeSetOption(int flag, boolean enabled) {}
 
     @Override
-    public void nativeSetLanguage(long controllerPtr, String language) {}
+    public void nativeSetLanguage(String language) {}
 
     @Override
-    public void nativeEnableAudioReturnChannel(long controllerPtr, int port, boolean flag) {}
+    public void nativeEnableAudioReturnChannel(int port, boolean flag) {}
 
     @Override
-    public boolean nativeIsConnected(long controllerPtr, int port) {
+    public boolean nativeIsConnected(int port) {
         return false;
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 2171d75..668f047 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1506,6 +1506,7 @@
                 .setRotation((mActivity.getWindowConfiguration().getRotation() + 1) % 4)
                 .build();
         setRotatedScreenOrientationSilently(mActivity);
+        mActivity.setVisible(false);
 
         final IWindowSession session = WindowManagerGlobal.getWindowSession();
         spyOn(session);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
index 4dbf79a..1d13788 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -456,6 +456,7 @@
     @Test
     public void testTransferStartingWindowSetFixedRotation() {
         final ActivityRecord topActivity = createTestActivityRecordForGivenTask(mTask);
+        topActivity.setVisible(false);
         mTask.positionChildAt(topActivity, POSITION_TOP);
         mActivity.addStartingWindow(mPackageName,
                 android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 6896740..105af4f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1081,6 +1081,7 @@
         mDisplayContent.onRequestedOverrideConfigurationChanged(config);
 
         final ActivityRecord app = mAppWindow.mActivityRecord;
+        app.setVisible(false);
         mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_ACTIVITY_OPEN,
                 false /* alwaysKeepCurrent */);
         mDisplayContent.mOpeningApps.add(app);
@@ -1135,6 +1136,7 @@
         // Launch another activity before the transition is finished.
         final ActivityRecord app2 = new ActivityTestsBase.StackBuilder(mWm.mRoot)
                 .setDisplay(mDisplayContent).build().getTopMostActivity();
+        app2.setVisible(false);
         mDisplayContent.mOpeningApps.add(app2);
         app2.setRequestedOrientation(newOrientation);
 
@@ -1277,6 +1279,14 @@
         mDisplayContent.setFixedRotationLaunchingAppUnchecked(mAppWindow.mActivityRecord);
         displayRotation.setRotation((displayRotation.getRotation() + 1) % 4);
         assertTrue(displayRotation.updateRotationUnchecked(false));
+
+        // The recents activity should not apply fixed rotation if the top activity is not opaque.
+        mDisplayContent.mFocusedApp = mAppWindow.mActivityRecord;
+        doReturn(false).when(mDisplayContent.mFocusedApp).occludesParent();
+        doReturn(ROTATION_90).when(mDisplayContent).rotationForActivityInDifferentOrientation(
+                eq(recentsActivity));
+        mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
+        assertFalse(recentsActivity.hasFixedRotationTransform());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 130e555..3c98272 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -517,6 +517,7 @@
         setUpApp(new TestDisplayContent.Builder(mService, dw, dh).setNotch(notchHeight).build());
         addStatusBar(mActivity.mDisplayContent);
 
+        mActivity.setVisible(false);
         mActivity.mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_ACTIVITY_OPEN,
                 false /* alwaysKeepCurrent */);
         mActivity.mDisplayContent.mOpeningApps.add(mActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 7ce0c1e..341e209 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -729,7 +729,7 @@
         // We should be rejected from the second sync since we are already
         // in one.
         assertEquals(false, bse.addToSyncSet(id2, task));
-        finishAndNotifyDrawing(w);
+        w.immediatelyNotifyBlastSync();
         assertEquals(true, bse.addToSyncSet(id2, task));
         bse.setReady(id2);
     }
@@ -753,7 +753,7 @@
         // Since we have a window we have to wait for it to draw to finish sync.
         verify(transactionListener, never())
             .onTransactionReady(anyInt(), any());
-        finishAndNotifyDrawing(w);
+        w.immediatelyNotifyBlastSync();
         verify(transactionListener)
             .onTransactionReady(anyInt(), any());
     }
@@ -821,14 +821,14 @@
         int id = bse.startSyncSet(transactionListener);
         assertEquals(true, bse.addToSyncSet(id, task));
         bse.setReady(id);
-        finishAndNotifyDrawing(w);
+        w.immediatelyNotifyBlastSync();
 
         // Since we have a child window we still shouldn't be done.
         verify(transactionListener, never())
             .onTransactionReady(anyInt(), any());
         reset(transactionListener);
 
-        finishAndNotifyDrawing(child);
+        child.immediatelyNotifyBlastSync();
         // Ah finally! Done
         verify(transactionListener)
                 .onTransactionReady(anyInt(), any());
@@ -1002,20 +1002,15 @@
         verify(mockCallback, never()).onTransactionReady(anyInt(), any());
         assertTrue(w1.useBLASTSync());
         assertTrue(w2.useBLASTSync());
-        finishAndNotifyDrawing(w1);
+        w1.immediatelyNotifyBlastSync();
 
         // Even though one Window finished drawing, both windows should still be using blast sync
         assertTrue(w1.useBLASTSync());
         assertTrue(w2.useBLASTSync());
 
-        finishAndNotifyDrawing(w2);
+        w2.immediatelyNotifyBlastSync();
         verify(mockCallback).onTransactionReady(anyInt(), any());
         assertFalse(w1.useBLASTSync());
         assertFalse(w2.useBLASTSync());
     }
-
-    private void finishAndNotifyDrawing(WindowState ws) {
-        ws.finishDrawing(null);
-        ws.notifyBlastSyncTransaction();
-    }
 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ee14608..8ae1ee9 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -38,6 +38,7 @@
 import android.annotation.TestApi;
 import android.annotation.WorkerThread;
 import android.app.PendingIntent;
+import android.app.role.RoleManager;
 import android.compat.Compatibility;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
@@ -1885,12 +1886,23 @@
      * Returns the unique device ID, for example, the IMEI for GSM and the MEID
      * or ESN for CDMA phones. Return null if device ID is not available.
      *
-     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
-     * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
-     * privileges (see {@link #hasCarrierPrivileges}) on any active subscription. The profile owner
-     * is an app that owns a managed profile on the device; for more details see <a
-     * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
-     * access is deprecated and will be removed in a future release.
+     * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
+     * restrictions, and apps are recommended to use resettable identifiers (see <a
+     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
+     * the following requirements is met:
+     * <ul>
+     *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
+     *     is a privileged permission that can only be granted to apps preloaded on the device.
+     *     <li>If the calling app is the device or profile owner and has been granted the
+     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
+     *     owns a managed profile on the device; for more details see <a
+     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}) on any
+     *     active subscription.
+     *     <li>If the calling app is the default SMS role holder (see {@link
+     *     RoleManager#isRoleHeld(String)}).
+     * </ul>
      *
      * <p>If the calling app does not meet one of these requirements then this method will behave
      * as follows:
@@ -1927,12 +1939,23 @@
      * Returns the unique device ID of a subscription, for example, the IMEI for
      * GSM and the MEID for CDMA phones. Return null if device ID is not available.
      *
-     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
-     * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
-     * privileges (see {@link #hasCarrierPrivileges}) on any active subscription. The profile owner
-     * is an app that owns a managed profile on the device; for more details see <a
-     * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
-     * access is deprecated and will be removed in a future release.
+     * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
+     * restrictions, and apps are recommended to use resettable identifiers (see <a
+     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
+     * the following requirements is met:
+     * <ul>
+     *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
+     *     is a privileged permission that can only be granted to apps preloaded on the device.
+     *     <li>If the calling app is the device or profile owner and has been granted the
+     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
+     *     owns a managed profile on the device; for more details see <a
+     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}) on any
+     *     active subscription.
+     *     <li>If the calling app is the default SMS role holder (see {@link
+     *     RoleManager#isRoleHeld(String)}).
+     * </ul>
      *
      * <p>If the calling app does not meet one of these requirements then this method will behave
      * as follows:
@@ -1985,18 +2008,23 @@
      * Returns the IMEI (International Mobile Equipment Identity). Return null if IMEI is not
      * available.
      *
-     * <p>This API requires one of the following:
+     * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
+     * restrictions, and apps are recommended to use resettable identifiers (see <a
+     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
+     * the following requirements is met:
      * <ul>
-     *     <li>The caller holds the READ_PRIVILEGED_PHONE_STATE permission.</li>
-     *     <li>If the caller is the device or profile owner, the caller holds the
-     *     {@link Manifest.permission#READ_PHONE_STATE} permission.</li>
-     *     <li>The caller has carrier privileges (see {@link #hasCarrierPrivileges()} on any
-     *     active subscription.</li>
-     *     <li>The caller is the default SMS app for the device.</li>
+     *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
+     *     is a privileged permission that can only be granted to apps preloaded on the device.
+     *     <li>If the calling app is the device or profile owner and has been granted the
+     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
+     *     owns a managed profile on the device; for more details see <a
+     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}) on any
+     *     active subscription.
+     *     <li>If the calling app is the default SMS role holder (see {@link
+     *     RoleManager#isRoleHeld(String)}).
      * </ul>
-     * <p>The profile owner is an app that owns a managed profile on the device; for more details
-     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
-     * Access by profile owners is deprecated and will be removed in a future release.
      *
      * <p>If the calling app does not meet one of these requirements then this method will behave
      * as follows:
@@ -2058,12 +2086,23 @@
     /**
      * Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available.
      *
-     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
-     * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
-     * privileges (see {@link #hasCarrierPrivileges}) on any active subscription. The profile owner
-     * is an app that owns a managed profile on the device; for more details see <a
-     * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
-     * access is deprecated and will be removed in a future release.
+     * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
+     * restrictions, and apps are recommended to use resettable identifiers (see <a
+     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
+     * the following requirements is met:
+     * <ul>
+     *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
+     *     is a privileged permission that can only be granted to apps preloaded on the device.
+     *     <li>If the calling app is the device or profile owner and has been granted the
+     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
+     *     owns a managed profile on the device; for more details see <a
+     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}) on any
+     *     active subscription.
+     *     <li>If the calling app is the default SMS role holder (see {@link
+     *     RoleManager#isRoleHeld(String)}).
+     * </ul>
      *
      * <p>If the calling app does not meet one of these requirements then this method will behave
      * as follows:
@@ -2085,12 +2124,23 @@
     /**
      * Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available.
      *
-     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
-     * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
-     * privileges (see {@link #hasCarrierPrivileges}) on any active subscription. The profile owner
-     * is an app that owns a managed profile on the device; for more details see <a
-     * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
-     * access is deprecated and will be removed in a future release.
+     * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
+     * restrictions, and apps are recommended to use resettable identifiers (see <a
+     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
+     * the following requirements is met:
+     * <ul>
+     *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
+     *     is a privileged permission that can only be granted to apps preloaded on the device.
+     *     <li>If the calling app is the device or profile owner and has been granted the
+     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
+     *     owns a managed profile on the device; for more details see <a
+     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}) on any
+     *     active subscription.
+     *     <li>If the calling app is the default SMS role holder (see {@link
+     *     RoleManager#isRoleHeld(String)}).
+     * </ul>
      *
      * <p>If the calling app does not meet one of these requirements then this method will behave
      * as follows:
@@ -2158,12 +2208,25 @@
     /**
      * Returns the Network Access Identifier (NAI). Return null if NAI is not available.
      *
-     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
-     * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
-     * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
-     * managed profile on the device; for more details see <a
-     * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
-     * access is deprecated and will be removed in a future release.
+     * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
+     * restrictions, and apps are recommended to use resettable identifiers (see <a
+     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
+     * the following requirements is met:
+     * <ul>
+     *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
+     *     is a privileged permission that can only be granted to apps preloaded on the device.
+     *     <li>If the calling app is the device or profile owner and has been granted the
+     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
+     *     owns a managed profile on the device; for more details see <a
+     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     *     <li>If the calling app is the default SMS role holder (see {@link
+     *     RoleManager#isRoleHeld(String)}).
+     * </ul>
+     *
+     * <p>If the calling app does not meet one of these requirements then this method will behave
+     * as follows:
      *
      * <ul>
      *     <li>If the calling app's target SDK is API level 28 or lower and the app has the
@@ -2182,12 +2245,25 @@
     /**
      * Returns the NAI. Return null if NAI is not available.
      *
-     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
-     * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
-     * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
-     * managed profile on the device; for more details see <a
-     * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
-     * access is deprecated and will be removed in a future release.
+     * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
+     * restrictions, and apps are recommended to use resettable identifiers (see <a
+     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
+     * the following requirements is met:
+     * <ul>
+     *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
+     *     is a privileged permission that can only be granted to apps preloaded on the device.
+     *     <li>If the calling app is the device or profile owner and has been granted the
+     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
+     *     owns a managed profile on the device; for more details see <a
+     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     *     <li>If the calling app is the default SMS role holder (see {@link
+     *     RoleManager#isRoleHeld(String)}).
+     * </ul>
+     *
+     * <p>If the calling app does not meet one of these requirements then this method will behave
+     * as follows:
      *
      * <ul>
      *     <li>If the calling app's target SDK is API level 28 or lower and the app has the
@@ -3775,12 +3851,22 @@
      * Returns the serial number of the SIM, if applicable. Return null if it is
      * unavailable.
      *
-     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
-     * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
-     * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
-     * managed profile on the device; for more details see <a
-     * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
-     * access is deprecated and will be removed in a future release.
+     * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
+     * restrictions, and apps are recommended to use resettable identifiers (see <a
+     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
+     * the following requirements is met:
+     * <ul>
+     *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
+     *     is a privileged permission that can only be granted to apps preloaded on the device.
+     *     <li>If the calling app is the device or profile owner and has been granted the
+     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
+     *     owns a managed profile on the device; for more details see <a
+     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     *     <li>If the calling app is the default SMS role holder (see {@link
+     *     RoleManager#isRoleHeld(String)}).
+     * </ul>
      *
      * <p>If the calling app does not meet one of these requirements then this method will behave
      * as follows:
@@ -3803,12 +3889,22 @@
      * Returns the serial number for the given subscription, if applicable. Return null if it is
      * unavailable.
      *
-     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
-     * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
-     * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
-     * managed profile on the device; for more details see <a
-     * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
-     * access is deprecated and will be removed in a future release.
+     * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
+     * restrictions, and apps are recommended to use resettable identifiers (see <a
+     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
+     * the following requirements is met:
+     * <ul>
+     *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
+     *     is a privileged permission that can only be granted to apps preloaded on the device.
+     *     <li>If the calling app is the device or profile owner and has been granted the
+     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
+     *     owns a managed profile on the device; for more details see <a
+     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     *     <li>If the calling app is the default SMS role holder (see {@link
+     *     RoleManager#isRoleHeld(String)}).
+     * </ul>
      *
      * <p>If the calling app does not meet one of these requirements then this method will behave
      * as follows:
@@ -4047,12 +4143,22 @@
      * Returns the unique subscriber ID, for example, the IMSI for a GSM phone.
      * Return null if it is unavailable.
      *
-     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
-     * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
-     * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
-     * managed profile on the device; for more details see <a
-     * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
-     * access is deprecated and will be removed in a future release.
+     * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
+     * restrictions, and apps are recommended to use resettable identifiers (see <a
+     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
+     * the following requirements is met:
+     * <ul>
+     *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
+     *     is a privileged permission that can only be granted to apps preloaded on the device.
+     *     <li>If the calling app is the device or profile owner and has been granted the
+     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
+     *     owns a managed profile on the device; for more details see <a
+     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     *     <li>If the calling app is the default SMS role holder (see {@link
+     *     RoleManager#isRoleHeld(String)}).
+     * </ul>
      *
      * <p>If the calling app does not meet one of these requirements then this method will behave
      * as follows:
@@ -4076,12 +4182,22 @@
      * for a subscription.
      * Return null if it is unavailable.
      *
-     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
-     * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
-     * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
-     * managed profile on the device; for more details see <a
-     * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
-     * access is deprecated and will be removed in a future release.
+     * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
+     * restrictions, and apps are recommended to use resettable identifiers (see <a
+     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
+     * the following requirements is met:
+     * <ul>
+     *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
+     *     is a privileged permission that can only be granted to apps preloaded on the device.
+     *     <li>If the calling app is the device or profile owner and has been granted the
+     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
+     *     owns a managed profile on the device; for more details see <a
+     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     *     <li>If the calling app is the default SMS role holder (see {@link
+     *     RoleManager#isRoleHeld(String)}).
+     * </ul>
      *
      * <p>If the calling app does not meet one of these requirements then this method will behave
      * as follows:
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index 2bcd4f4..a5e76e6 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -885,7 +885,8 @@
 
         /**
          * Configure the Soft AP to require manual user control of client association.
-         * If disabled (the default) then any client can associate to this Soft AP using the
+         * If disabled (the default) then any client which isn't in the blocked list
+         * {@link #getBlockedClientList()} can associate to this Soft AP using the
          * correct credentials until the Soft AP capacity is reached (capacity is hardware, carrier,
          * or user limited - using {@link #setMaxNumberOfClients(int)}).
          *
@@ -945,21 +946,19 @@
         }
 
         /**
-         * This method together with {@link setClientControlByUserEnabled(boolean)} control client
-         * connections to the AP. If client control by user is disabled using the above method then
-         * this API has no effect and clients are allowed to associate to the AP (within limit of
-         * max number of clients).
+         * This API configures the list of clients which are blocked and cannot associate
+         * to the Soft AP.
          *
-         * If client control by user is enabled then this API this API configures the list of
-         * clients which are blocked. These are rejected.
+         * <p>
+         * This method requires hardware support. Hardware support can be determined using
+         * {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
+         * {@link SoftApCapability#areFeaturesSupported(int)}
+         * with {@link SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT}
          *
-         * All other clients which attempt to associate, whose MAC addresses are on neither list,
-         * are:
-         * <ul>
-         * <li>Rejected</li>
-         * <li>A callback {@link WifiManager.SoftApCallback#onBlockedClientConnecting(WifiClient)}
-         * is issued (which allows the user to add them to the allowed client list if desired).<li>
-         * </ul>
+         * <p>
+         * If the method is called on a device without hardware support then starting the soft AP
+         * using {@link WifiManager#startTetheredHotspot(SoftApConfiguration)} will fail with
+         * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
          *
          * @param blockedClientList list of clients which are not allowed to associate to the AP.
          * @return Builder for chaining.
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index d35ce3c..77fa673 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -93,6 +93,8 @@
     public static final String OPP_KEY_CACHING     = "proactive_key_caching";
     /** @hide */
     public static final String EAP_ERP             = "eap_erp";
+    /** @hide */
+    public static final String OCSP                = "ocsp";
 
     /**
      * String representing the keystore OpenSSL ENGINE's ID.