Merge "Fix invalid bubble predicate once and for all" 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/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
index 4d29045..3d4154a2 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
@@ -398,6 +398,26 @@
         return revocableFd.getRevocableFileDescriptor();
     }
 
+    void destroy() {
+        revokeAllFds();
+        getBlobFile().delete();
+    }
+
+    private void revokeAllFds() {
+        synchronized (mRevocableFds) {
+            for (int i = 0, pkgCount = mRevocableFds.size(); i < pkgCount; ++i) {
+                final ArraySet<RevocableFileDescriptor> packageFds =
+                        mRevocableFds.valueAt(i);
+                if (packageFds == null) {
+                    continue;
+                }
+                for (int j = 0, fdCount = packageFds.size(); j < fdCount; ++j) {
+                    packageFds.valueAt(j).revoke();
+                }
+            }
+        }
+    }
+
     boolean shouldBeDeleted(boolean respectLeaseWaitTime) {
         // Expired data blobs
         if (getBlobHandle().isExpired()) {
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..f7468d8 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(
@@ -556,7 +606,11 @@
                     UserHandle.getUserId(callingUid));
             userBlobs.entrySet().removeIf(entry -> {
                 final BlobMetadata blobMetadata = entry.getValue();
-                return blobMetadata.getBlobId() == blobId;
+                if (blobMetadata.getBlobId() == blobId) {
+                    deleteBlobLocked(blobMetadata);
+                    return true;
+                }
+                return false;
             });
             writeBlobsInfoAsync();
         }
@@ -607,11 +661,10 @@
         switch (session.getState()) {
             case STATE_ABANDONED:
             case STATE_VERIFIED_INVALID:
-                session.getSessionFile().delete();
                 synchronized (mBlobsLock) {
+                    deleteSessionLocked(session);
                     getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid()))
                             .remove(session.getSessionId());
-                    mActiveBlobIds.remove(session.getSessionId());
                     if (LOGV) {
                         Slog.v(TAG, "Session is invalid; deleted " + session);
                     }
@@ -626,6 +679,17 @@
                 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);
+                        deleteSessionLocked(session);
+                        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 +720,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);
@@ -670,8 +734,7 @@
                     }
                     // Delete redundant data from recommits.
                     if (session.getSessionId() != blob.getBlobId()) {
-                        session.getSessionFile().delete();
-                        mActiveBlobIds.remove(session.getSessionId());
+                        deleteSessionLocked(session);
                     }
                     getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid()))
                             .remove(session.getSessionId());
@@ -957,8 +1020,7 @@
             userSessions.removeIf((sessionId, blobStoreSession) -> {
                 if (blobStoreSession.getOwnerUid() == uid
                         && blobStoreSession.getOwnerPackageName().equals(packageName)) {
-                    blobStoreSession.getSessionFile().delete();
-                    mActiveBlobIds.remove(blobStoreSession.getSessionId());
+                    deleteSessionLocked(blobStoreSession);
                     return true;
                 }
                 return false;
@@ -999,8 +1061,7 @@
             if (userSessions != null) {
                 for (int i = 0, count = userSessions.size(); i < count; ++i) {
                     final BlobStoreSession session = userSessions.valueAt(i);
-                    session.getSessionFile().delete();
-                    mActiveBlobIds.remove(session.getSessionId());
+                    deleteSessionLocked(session);
                 }
             }
 
@@ -1076,8 +1137,7 @@
                 }
 
                 if (shouldRemove) {
-                    blobStoreSession.getSessionFile().delete();
-                    mActiveBlobIds.remove(blobStoreSession.getSessionId());
+                    deleteSessionLocked(blobStoreSession);
                     deletedBlobIds.add(blobStoreSession.getSessionId());
                 }
                 return shouldRemove;
@@ -1089,13 +1149,29 @@
     }
 
     @GuardedBy("mBlobsLock")
+    private void deleteSessionLocked(BlobStoreSession blobStoreSession) {
+        blobStoreSession.destroy();
+        mActiveBlobIds.remove(blobStoreSession.getSessionId());
+    }
+
+    @GuardedBy("mBlobsLock")
     private void deleteBlobLocked(BlobMetadata blobMetadata) {
-        blobMetadata.getBlobFile().delete();
+        blobMetadata.destroy();
         mActiveBlobIds.remove(blobMetadata.getBlobId());
     }
 
     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 +1183,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 +1417,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..2f83be1 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);
         }
     }
@@ -468,6 +479,11 @@
         }
     }
 
+    void destroy() {
+        revokeAllFds();
+        getSessionFile().delete();
+    }
+
     private void revokeAllFds() {
         synchronized (mRevocableFds) {
             for (int i = mRevocableFds.size() - 1; i >= 0; --i) {
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index cc3017a..0c8c9a9 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -870,6 +870,14 @@
      */
     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.
 
@@ -880,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 = {
@@ -1309,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;
@@ -1674,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
@@ -1916,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;
@@ -2102,6 +2120,7 @@
                 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.
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 a828aac..86a3579 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1900,13 +1900,11 @@
 
     @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:" + getOuterContext();
+                    + " from a non-visual Context. ";
             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 "
@@ -2371,7 +2369,6 @@
         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/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/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index ffeeb80..0d2d4d1 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -500,13 +500,12 @@
      */
     public static ViewConfiguration get(Context context) {
         if (!context.isUiContext() && vmIncorrectContextUseEnabled()) {
-            final String errorMessage = "Tried to access UI constants from a non-visual Context:"
-                    + context;
+            final String errorMessage = "Tried to access UI constants from a non-visual 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/widget/inline/InlineContentView.java b/core/java/android/widget/inline/InlineContentView.java
index 1b666aa..9712311 100644
--- a/core/java/android/widget/inline/InlineContentView.java
+++ b/core/java/android/widget/inline/InlineContentView.java
@@ -21,8 +21,8 @@
 import android.annotation.TestApi;
 import android.content.Context;
 import android.graphics.PixelFormat;
-import android.graphics.Rect;
 import android.graphics.PointF;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.SurfaceControl;
@@ -156,7 +156,8 @@
         @Override
         public void onDraw() {
             computeParentPositionAndScale();
-            mSurfaceView.setVisibility(VISIBLE);
+            final int visibility = InlineContentView.this.isShown() ? VISIBLE : GONE;
+            mSurfaceView.setVisibility(visibility);
         }
     };
 
diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml
index 82e99e6..861a056 100644
--- a/core/res/res/layout/notification_template_material_conversation.xml
+++ b/core/res/res/layout/notification_template_material_conversation.xml
@@ -128,6 +128,9 @@
                 android:layout_weight="1">
 
                 <!-- Header -->
+
+                <!-- Use layout_marginStart instead of paddingStart to work around strange
+                     measurement behavior on lower display densities. -->
                 <LinearLayout
                     android:id="@+id/conversation_header"
                     android:layout_width="wrap_content"
@@ -135,11 +138,11 @@
                     android:orientation="horizontal"
                     android:paddingTop="16dp"
                     android:layout_marginBottom="2dp"
-                    android:paddingStart="@dimen/conversation_content_start"
+                    android:layout_marginStart="@dimen/conversation_content_start"
                 >
                     <TextView
                         android:id="@+id/conversation_text"
-                        android:layout_width="0dp"
+                        android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
                         android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
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/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index b615885..6dc8322 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -137,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;
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/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index be85906..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;
@@ -9202,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/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f8ab6f4..5e908b2 100755
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -422,6 +422,9 @@
 
     private final boolean mUseFixedVolume;
 
+    // If absolute volume is supported in AVRCP device
+    private volatile boolean mAvrcpAbsVolSupported = false;
+
     /**
     * Default stream type used for volume control in the absence of playback
     * e.g. user on homescreen, no app playing anything, presses hardware volume buttons, this
@@ -4994,7 +4997,7 @@
             return AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE;
         }
         if (audioSystemDeviceOut == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP
-                && mDeviceBroker.isAvrcpAbsoluteVolumeSupported()) {
+                && mAvrcpAbsVolSupported) {
             return AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE;
         }
         return AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE;
@@ -5687,12 +5690,12 @@
         }
 
         // must be called while synchronized VolumeStreamState.class
-        /*package*/ void applyDeviceVolume_syncVSS(int device, boolean isAvrcpAbsVolSupported) {
+        /*package*/ void applyDeviceVolume_syncVSS(int device) {
             int index;
             if (isFullyMuted()) {
                 index = 0;
             } else if (AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
-                    && isAvrcpAbsVolSupported) {
+                    && mAvrcpAbsVolSupported) {
                 index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
             } else if (isFullVolumeDevice(device)) {
                 index = (mIndexMax + 5)/10;
@@ -5705,7 +5708,6 @@
         }
 
         public void applyAllVolumes() {
-            final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported();
             synchronized (VolumeStreamState.class) {
                 // apply device specific volumes first
                 int index;
@@ -5715,7 +5717,7 @@
                         if (isFullyMuted()) {
                             index = 0;
                         } else if (AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
-                                && isAvrcpAbsVolSupported) {
+                                && mAvrcpAbsVolSupported) {
                             index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
                         } else if (isFullVolumeDevice(device)) {
                             index = (mIndexMax + 5)/10;
@@ -5955,7 +5957,6 @@
         }
 
         public void checkFixedVolumeDevices() {
-            final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported();
             synchronized (VolumeStreamState.class) {
                 // ignore settings for fixed volume devices: volume should always be at max or 0
                 if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) {
@@ -5966,7 +5967,7 @@
                                 || (isFixedVolumeDevice(device) && index != 0)) {
                             mIndexMap.put(device, mIndexMax);
                         }
-                        applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported);
+                        applyDeviceVolume_syncVSS(device);
                     }
                 }
             }
@@ -6130,11 +6131,9 @@
 
     /*package*/ void setDeviceVolume(VolumeStreamState streamState, int device) {
 
-        final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported();
-
         synchronized (VolumeStreamState.class) {
             // Apply volume
-            streamState.applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported);
+            streamState.applyDeviceVolume_syncVSS(device);
 
             // Apply change to all streams using this one as alias
             int numStreamTypes = AudioSystem.getNumStreamTypes();
@@ -6144,13 +6143,11 @@
                     // Make sure volume is also maxed out on A2DP device for aliased stream
                     // that may have a different device selected
                     int streamDevice = getDeviceForStream(streamType);
-                    if ((device != streamDevice) && isAvrcpAbsVolSupported
+                    if ((device != streamDevice) && mAvrcpAbsVolSupported
                             && AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)) {
-                        mStreamStates[streamType].applyDeviceVolume_syncVSS(device,
-                                isAvrcpAbsVolSupported);
+                        mStreamStates[streamType].applyDeviceVolume_syncVSS(device);
                     }
-                    mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice,
-                            isAvrcpAbsVolSupported);
+                    mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice);
                 }
             }
         }
@@ -6458,6 +6455,7 @@
         // address is not used for now, but may be used when multiple a2dp devices are supported
         sVolumeLogger.log(new AudioEventLogger.StringEvent("avrcpSupportsAbsoluteVolume addr="
                 + address + " support=" + support));
+        mAvrcpAbsVolSupported = support;
         mDeviceBroker.setAvrcpAbsoluteVolumeSupported(support);
         sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
                     AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0,
@@ -7415,8 +7413,7 @@
         pw.print("  mCameraSoundForced="); pw.println(mCameraSoundForced);
         pw.print("  mHasVibrator="); pw.println(mHasVibrator);
         pw.print("  mVolumePolicy="); pw.println(mVolumePolicy);
-        pw.print("  mAvrcpAbsVolSupported=");
-        pw.println(mDeviceBroker.isAvrcpAbsoluteVolumeSupported());
+        pw.print("  mAvrcpAbsVolSupported="); pw.println(mAvrcpAbsVolSupported);
         pw.print("  mIsSingleVolume="); pw.println(mIsSingleVolume);
         pw.print("  mUseFixedVolume="); pw.println(mUseFixedVolume);
         pw.print("  mFixedVolumeDevices="); pw.println(dumpDeviceTypes(mFixedVolumeDevices));
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index de8ad6b..994cec2 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -401,6 +401,7 @@
 
     private boolean mDataLoaderFinished = false;
 
+    // TODO(b/159663586): should be protected by mLock
     private IncrementalFileStorages mIncrementalFileStorages;
 
     private static final FileFilter sAddedApkFilter = new FileFilter() {
@@ -1353,7 +1354,7 @@
     private boolean markAsSealed(@NonNull IntentSender statusReceiver, boolean forTransfer) {
         Objects.requireNonNull(statusReceiver);
 
-        List<PackageInstallerSession> childSessions = getChildSessions();
+        List<PackageInstallerSession> childSessions = getChildSessionsNotLocked();
 
         synchronized (mLock) {
             assertCallerIsOwnerOrRootLocked();
@@ -1436,7 +1437,11 @@
      *
      * <p> This method is handy to prevent potential deadlocks (b/123391593)
      */
-    private @Nullable List<PackageInstallerSession> getChildSessions() {
+    private @Nullable List<PackageInstallerSession> getChildSessionsNotLocked() {
+        if (Thread.holdsLock(mLock)) {
+            Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
+                    + " is holding mLock", new Throwable());
+        }
         List<PackageInstallerSession> childSessions = null;
         if (isMultiPackage()) {
             final int[] childSessionIds = getChildSessionIds();
@@ -1605,7 +1610,7 @@
                 return;
             }
         }
-        List<PackageInstallerSession> childSessions = getChildSessions();
+        List<PackageInstallerSession> childSessions = getChildSessionsNotLocked();
         synchronized (mLock) {
             try {
                 sealLocked(childSessions);
@@ -1649,7 +1654,7 @@
             throw new SecurityException("Can only transfer sessions that use public options");
         }
 
-        List<PackageInstallerSession> childSessions = getChildSessions();
+        List<PackageInstallerSession> childSessions = getChildSessionsNotLocked();
 
         synchronized (mLock) {
             assertCallerIsOwnerOrRootLocked();
@@ -1701,7 +1706,7 @@
         // outside of the lock, because reading the child
         // sessions with the lock held could lead to deadlock
         // (b/123391593).
-        List<PackageInstallerSession> childSessions = getChildSessions();
+        List<PackageInstallerSession> childSessions = getChildSessionsNotLocked();
 
         try {
             synchronized (mLock) {
@@ -2602,6 +2607,8 @@
                     "Session " + sessionId + " is a child of multi-package session "
                             + mParentSessionId +  " and may not be abandoned directly.");
         }
+
+        List<PackageInstallerSession> childSessions = getChildSessionsNotLocked();
         synchronized (mLock) {
             if (params.isStaged && mDestroyed) {
                 // If a user abandons staged session in an unsafe state, then system will try to
@@ -2625,7 +2632,7 @@
                     mCallback.onStagedSessionChanged(this);
                     return;
                 }
-                cleanStageDir();
+                cleanStageDir(childSessions);
             }
 
             if (mRelinquished) {
@@ -3055,7 +3062,7 @@
             mStagedSessionErrorMessage = errorMessage;
             Slog.d(TAG, "Marking session " + sessionId + " as failed: " + errorMessage);
         }
-        cleanStageDir();
+        cleanStageDirNotLocked();
         mCallback.onStagedSessionChanged(this);
     }
 
@@ -3070,7 +3077,7 @@
             mStagedSessionErrorMessage = "";
             Slog.d(TAG, "Marking session " + sessionId + " as applied");
         }
-        cleanStageDir();
+        cleanStageDirNotLocked();
         mCallback.onStagedSessionChanged(this);
     }
 
@@ -3128,20 +3135,37 @@
         }
     }
 
-    private void cleanStageDir() {
-        if (isMultiPackage()) {
-            for (int childSessionId : getChildSessionIds()) {
-                mSessionProvider.getSession(childSessionId).cleanStageDir();
+    /**
+     * <b>must not hold {@link #mLock}</b>
+     */
+    private void cleanStageDirNotLocked() {
+        if (Thread.holdsLock(mLock)) {
+            Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
+                    + " is holding mLock", new Throwable());
+        }
+        cleanStageDir(getChildSessionsNotLocked());
+    }
+
+    private void cleanStageDir(List<PackageInstallerSession> childSessions) {
+        if (childSessions != null) {
+            for (PackageInstallerSession childSession : childSessions) {
+                if (childSession != null) {
+                    childSession.cleanStageDir();
+                }
             }
         } else {
-            if (mIncrementalFileStorages != null) {
-                mIncrementalFileStorages.cleanUp();
-                mIncrementalFileStorages = null;
-            }
-            try {
-                mPm.mInstaller.rmPackageDir(stageDir.getAbsolutePath());
-            } catch (InstallerException ignored) {
-            }
+            cleanStageDir();
+        }
+    }
+
+    private void cleanStageDir() {
+        if (mIncrementalFileStorages != null) {
+            mIncrementalFileStorages.cleanUp();
+            mIncrementalFileStorages = null;
+        }
+        try {
+            mPm.mInstaller.rmPackageDir(stageDir.getAbsolutePath());
+        } catch (InstallerException ignored) {
         }
     }
 
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 8879ccb..4f0b689 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -330,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() {
@@ -473,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 */
                 }
@@ -1529,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 + " "
@@ -1632,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()) {
@@ -1706,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);
@@ -1819,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);
             }
         }
 
@@ -2300,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);
@@ -3886,7 +3892,7 @@
                             PackageManagerServiceUtils.getPermissionsState(mPackageManagerInt,
                                     pkg);
                     if (!newPermissionsState.hasPermission(permission, userId)) {
-                        callback.onPermissionRevoked(pkg.getUid(), userId);
+                        callback.onPermissionRevoked(pkg.getUid(), userId, null);
                         break;
                     }
                 }
@@ -4245,7 +4251,7 @@
                         overridePolicy,
                         Process.SYSTEM_UID,
                         userId,
-                        callback);
+                        null, callback);
             } catch (IllegalArgumentException e) {
                 Slog.e(TAG,
                         "Failed to revoke "
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/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/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 3077997..25842f5 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2638,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/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 1762b62..c8d9fe0 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -351,13 +351,9 @@
     }
 
     private void updateVisibility() {
-        // TODO(b/159699383): remove the client controlled check when the insets visibility can be
-        //                    driven by the system UI.
         final boolean isClientControlled = mControlTarget != null
                 && mControlTarget.isClientControlled();
-        mSource.setVisible(mServerVisible
-                && ((!isClientControlled && mDisplayContent.inMultiWindowMode())
-                    || mClientVisible));
+        mSource.setVisible(mServerVisible && (!isClientControlled || mClientVisible));
         ProtoLog.d(WM_DEBUG_IME,
                 "InsetsSource updateVisibility serverVisible: %s clientVisible: %s",
                 mServerVisible, mClientVisible);
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 7b69038..e230807 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -564,16 +564,23 @@
             // Apps and their containers are not allowed to specify an orientation while using
             // root tasks...except for the home stack if it is not resizable and currently
             // visible (top of) its root task.
-            if (mRootHomeTask != null && mRootHomeTask.isVisible()
-                    && !mRootHomeTask.isResizeable()) {
+            if (mRootHomeTask != null && !mRootHomeTask.isResizeable()) {
                 // Manually nest one-level because because getOrientation() checks fillsParent()
                 // which checks that requestedOverrideBounds() is empty. However, in this case,
                 // it is not empty because it's been overridden to maintain the fullscreen size
                 // within a smaller split-root.
                 final Task topHomeTask = mRootHomeTask.getTopMostTask();
-                final int orientation = topHomeTask.getOrientation();
-                if (orientation != SCREEN_ORIENTATION_UNSET) {
-                    return orientation;
+                final ActivityRecord topHomeActivity = topHomeTask.getTopNonFinishingActivity();
+                // If a home activity is in the process of launching and isn't yet visible we
+                // should still respect the stack's preferred orientation to ensure rotation occurs
+                // before the home activity finishes launching.
+                final boolean isHomeActivityLaunching = topHomeActivity != null
+                        && topHomeActivity.mVisibleRequested;
+                if (topHomeTask.isVisible() || isHomeActivityLaunching) {
+                    final int orientation = topHomeTask.getOrientation();
+                    if (orientation != SCREEN_ORIENTATION_UNSET) {
+                        return orientation;
+                    }
                 }
             }
             return SCREEN_ORIENTATION_UNSPECIFIED;
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/blob/BlobStoreManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
index 7446289..4e2f9a4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
@@ -174,10 +174,10 @@
         mService.handlePackageRemoved(TEST_PKG1, TEST_UID1);
 
         // Verify sessions are removed
-        verify(sessionFile1).delete();
-        verify(sessionFile2, never()).delete();
-        verify(sessionFile3, never()).delete();
-        verify(sessionFile4).delete();
+        verify(session1).destroy();
+        verify(session2, never()).destroy();
+        verify(session3, never()).destroy();
+        verify(session4).destroy();
 
         assertThat(mUserSessions.size()).isEqualTo(2);
         assertThat(mUserSessions.get(sessionId1)).isNull();
@@ -193,9 +193,9 @@
         verify(blobMetadata3).removeCommitter(TEST_PKG1, TEST_UID1);
         verify(blobMetadata3).removeLeasee(TEST_PKG1, TEST_UID1);
 
-        verify(blobFile1, never()).delete();
-        verify(blobFile2).delete();
-        verify(blobFile3).delete();
+        verify(blobMetadata1, never()).destroy();
+        verify(blobMetadata2).destroy();
+        verify(blobMetadata3).destroy();
 
         assertThat(mUserBlobs.size()).isEqualTo(1);
         assertThat(mUserBlobs.get(blobHandle1)).isNotNull();
@@ -272,9 +272,9 @@
         mService.handleIdleMaintenanceLocked();
 
         // Verify stale sessions are removed
-        verify(sessionFile1).delete();
-        verify(sessionFile2, never()).delete();
-        verify(sessionFile3).delete();
+        verify(session1).destroy();
+        verify(session2, never()).destroy();
+        verify(session3).destroy();
 
         assertThat(mUserSessions.size()).isEqualTo(1);
         assertThat(mUserSessions.get(sessionId2)).isNotNull();
@@ -317,9 +317,9 @@
         mService.handleIdleMaintenanceLocked();
 
         // Verify stale blobs are removed
-        verify(blobFile1).delete();
-        verify(blobFile2, never()).delete();
-        verify(blobFile3).delete();
+        verify(blobMetadata1).destroy();
+        verify(blobMetadata2, never()).destroy();
+        verify(blobMetadata3).destroy();
 
         assertThat(mUserBlobs.size()).isEqualTo(1);
         assertThat(mUserBlobs.get(blobHandle2)).isNotNull();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index 786f8d8..8c3661b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -28,14 +28,17 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -207,6 +210,40 @@
                 false /* reuseCandidate */);
     }
 
+    @Test
+    public void testGetOrientation_nonResizableHomeStackWithHomeActivityPendingVisibilityChange() {
+        final RootWindowContainer rootWindowContainer = mWm.mAtmService.mRootWindowContainer;
+        final TaskDisplayArea defaultTaskDisplayArea =
+                rootWindowContainer.getDefaultTaskDisplayArea();
+
+        final ActivityStack rootHomeTask = defaultTaskDisplayArea.getRootHomeTask();
+        rootHomeTask.mResizeMode = RESIZE_MODE_UNRESIZEABLE;
+
+        final ActivityStack primarySplitTask =
+                new ActivityTestsBase.StackBuilder(rootWindowContainer)
+                .setTaskDisplayArea(defaultTaskDisplayArea)
+                .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .setOnTop(true)
+                .setCreateActivity(true)
+                .build();
+        ActivityRecord primarySplitActivity = primarySplitTask.getTopNonFinishingActivity();
+        assertNotNull(primarySplitActivity);
+        primarySplitActivity.setState(RESUMED,
+                "testGetOrientation_nonResizableHomeStackWithHomeActivityPendingVisibilityChange");
+
+        ActivityRecord homeActivity = rootHomeTask.getTopNonFinishingActivity();
+        if (homeActivity == null) {
+            homeActivity = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+                    .setStack(rootHomeTask).setCreateTask(true).build();
+        }
+        homeActivity.setVisible(false);
+        homeActivity.mVisibleRequested = true;
+        assertFalse(rootHomeTask.isVisible());
+
+        assertEquals(rootWindowContainer.getOrientation(), rootHomeTask.getOrientation());
+    }
+
     private void assertGetOrCreateStack(int windowingMode, int activityType, Task candidateTask,
             boolean reuseCandidate) {
         final TaskDisplayArea taskDisplayArea = candidateTask.getDisplayArea();
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();
-    }
 }