Merge "Add subsample offset to the exposed MediaFormat" into rvc-dev
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
index 483d2cc..9c1acaf 100644
--- a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
+++ b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
@@ -184,9 +184,8 @@
      * @throws SecurityException when the caller is not allowed to create a session, such
      *                           as when called from an Instant app.
      * @throws IllegalArgumentException when {@code blobHandle} is invalid.
-     * @throws IllegalStateException when a new session could not be created, such as when the
-     *                               caller is trying to create too many sessions or when the
-     *                               device is running low on space.
+     * @throws LimitExceededException when a new session could not be created, such as when the
+     *                                caller is trying to create too many sessions.
      */
     public @IntRange(from = 1) long createSession(@NonNull BlobHandle blobHandle)
             throws IOException {
@@ -194,6 +193,7 @@
             return mService.createSession(blobHandle, mContext.getOpPackageName());
         } catch (ParcelableException e) {
             e.maybeRethrow(IOException.class);
+            e.maybeRethrow(LimitExceededException.class);
             throw new RuntimeException(e);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -302,8 +302,9 @@
      *                                  if the {@code leaseExpiryTimeMillis} is greater than the
      *                                  {@link BlobHandle#getExpiryTimeMillis()}.
      * @throws LimitExceededException when a lease could not be acquired, such as when the
-     *                                caller is trying to acquire leases on too much data. Apps
-     *                                can avoid this by checking the remaining quota using
+     *                                caller is trying to acquire too many leases or acquire
+     *                                leases on too much data. Apps can avoid this by checking
+     *                                the remaining quota using
      *                                {@link #getRemainingLeaseQuotaBytes()} before trying to
      *                                acquire a lease.
      *
@@ -362,8 +363,9 @@
      *                                  if the {@code leaseExpiryTimeMillis} is greater than the
      *                                  {@link BlobHandle#getExpiryTimeMillis()}.
      * @throws LimitExceededException when a lease could not be acquired, such as when the
-     *                                caller is trying to acquire leases on too much data. Apps
-     *                                can avoid this by checking the remaining quota using
+     *                                caller is trying to acquire too many leases or acquire
+     *                                leases on too much data. Apps can avoid this by checking
+     *                                the remaining quota using
      *                                {@link #getRemainingLeaseQuotaBytes()} before trying to
      *                                acquire a lease.
      *
@@ -415,8 +417,9 @@
      *                           exist or the caller does not have access to it.
      * @throws IllegalArgumentException when {@code blobHandle} is invalid.
      * @throws LimitExceededException when a lease could not be acquired, such as when the
-     *                                caller is trying to acquire leases on too much data. Apps
-     *                                can avoid this by checking the remaining quota using
+     *                                caller is trying to acquire too many leases or acquire
+     *                                leases on too much data. Apps can avoid this by checking
+     *                                the remaining quota using
      *                                {@link #getRemainingLeaseQuotaBytes()} before trying to
      *                                acquire a lease.
      *
@@ -462,8 +465,9 @@
      *                           exist or the caller does not have access to it.
      * @throws IllegalArgumentException when {@code blobHandle} is invalid.
      * @throws LimitExceededException when a lease could not be acquired, such as when the
-     *                                caller is trying to acquire leases on too much data. Apps
-     *                                can avoid this by checking the remaining quota using
+     *                                caller is trying to acquire too many leases or acquire
+     *                                leases on too much data. Apps can avoid this by checking
+     *                                the remaining quota using
      *                                {@link #getRemainingLeaseQuotaBytes()} before trying to
      *                                acquire a lease.
      *
@@ -757,6 +761,8 @@
          * @throws SecurityException when the caller is not the owner of the session.
          * @throws IllegalStateException when the caller tries to change access for a blob which is
          *                               already committed.
+         * @throws LimitExceededException when the caller tries to explicitly allow too
+         *                                many packages using this API.
          */
         public void allowPackageAccess(@NonNull String packageName, @NonNull byte[] certificate)
                 throws IOException {
@@ -764,6 +770,7 @@
                 mSession.allowPackageAccess(packageName, certificate);
             } catch (ParcelableException e) {
                 e.maybeRethrow(IOException.class);
+                e.maybeRethrow(LimitExceededException.class);
                 throw new RuntimeException(e);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
index 265479f..79cd1b1 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
@@ -141,6 +141,36 @@
         public static long DELETE_ON_LAST_LEASE_DELAY_MS =
                 DEFAULT_DELETE_ON_LAST_LEASE_DELAY_MS;
 
+        /**
+         * Denotes the maximum number of active sessions per app at any time.
+         */
+        public static final String KEY_MAX_ACTIVE_SESSIONS = "max_active_sessions";
+        public static int DEFAULT_MAX_ACTIVE_SESSIONS = 250;
+        public static int MAX_ACTIVE_SESSIONS = DEFAULT_MAX_ACTIVE_SESSIONS;
+
+        /**
+         * Denotes the maximum number of committed blobs per app at any time.
+         */
+        public static final String KEY_MAX_COMMITTED_BLOBS = "max_committed_blobs";
+        public static int DEFAULT_MAX_COMMITTED_BLOBS = 1000;
+        public static int MAX_COMMITTED_BLOBS = DEFAULT_MAX_COMMITTED_BLOBS;
+
+        /**
+         * Denotes the maximum number of leased blobs per app at any time.
+         */
+        public static final String KEY_MAX_LEASED_BLOBS = "max_leased_blobs";
+        public static int DEFAULT_MAX_LEASED_BLOBS = 500;
+        public static int MAX_LEASED_BLOBS = DEFAULT_MAX_LEASED_BLOBS;
+
+        /**
+         * Denotes the maximum number of packages explicitly permitted to access a blob
+         * (permitted as part of creating a {@link BlobAccessMode}).
+         */
+        public static final String KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES = "max_permitted_pks";
+        public static int DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES = 300;
+        public static int MAX_BLOB_ACCESS_PERMITTED_PACKAGES =
+                DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES;
+
         static void refresh(Properties properties) {
             if (!NAMESPACE_BLOBSTORE.equals(properties.getNamespace())) {
                 return;
@@ -178,6 +208,19 @@
                         DELETE_ON_LAST_LEASE_DELAY_MS = properties.getLong(key,
                                 DEFAULT_DELETE_ON_LAST_LEASE_DELAY_MS);
                         break;
+                    case KEY_MAX_ACTIVE_SESSIONS:
+                        MAX_ACTIVE_SESSIONS = properties.getInt(key, DEFAULT_MAX_ACTIVE_SESSIONS);
+                        break;
+                    case KEY_MAX_COMMITTED_BLOBS:
+                        MAX_COMMITTED_BLOBS = properties.getInt(key, DEFAULT_MAX_COMMITTED_BLOBS);
+                        break;
+                    case KEY_MAX_LEASED_BLOBS:
+                        MAX_LEASED_BLOBS = properties.getInt(key, DEFAULT_MAX_LEASED_BLOBS);
+                        break;
+                    case KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES:
+                        MAX_BLOB_ACCESS_PERMITTED_PACKAGES = properties.getInt(key,
+                                DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES);
+                        break;
                     default:
                         Slog.wtf(TAG, "Unknown key in device config properties: " + key);
                 }
@@ -210,6 +253,15 @@
             fout.println(String.format(dumpFormat, KEY_DELETE_ON_LAST_LEASE_DELAY_MS,
                     TimeUtils.formatDuration(DELETE_ON_LAST_LEASE_DELAY_MS),
                     TimeUtils.formatDuration(DEFAULT_DELETE_ON_LAST_LEASE_DELAY_MS)));
+            fout.println(String.format(dumpFormat, KEY_MAX_ACTIVE_SESSIONS,
+                    MAX_ACTIVE_SESSIONS, DEFAULT_MAX_ACTIVE_SESSIONS));
+            fout.println(String.format(dumpFormat, KEY_MAX_COMMITTED_BLOBS,
+                    MAX_COMMITTED_BLOBS, DEFAULT_MAX_COMMITTED_BLOBS));
+            fout.println(String.format(dumpFormat, KEY_MAX_LEASED_BLOBS,
+                    MAX_LEASED_BLOBS, DEFAULT_MAX_LEASED_BLOBS));
+            fout.println(String.format(dumpFormat, KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES,
+                    MAX_BLOB_ACCESS_PERMITTED_PACKAGES,
+                    DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES));
         }
     }
 
@@ -288,6 +340,34 @@
         return DeviceConfigProperties.DELETE_ON_LAST_LEASE_DELAY_MS;
     }
 
+    /**
+     * Returns the maximum number of active sessions per app.
+     */
+    public static int getMaxActiveSessions() {
+        return DeviceConfigProperties.MAX_ACTIVE_SESSIONS;
+    }
+
+    /**
+     * Returns the maximum number of committed blobs per app.
+     */
+    public static int getMaxCommittedBlobs() {
+        return DeviceConfigProperties.MAX_COMMITTED_BLOBS;
+    }
+
+    /**
+     * Returns the maximum number of leased blobs per app.
+     */
+    public static int getMaxLeasedBlobs() {
+        return DeviceConfigProperties.MAX_LEASED_BLOBS;
+    }
+
+    /**
+     * Returns the maximum number of packages explicitly permitted to access a blob.
+     */
+    public static int getMaxPermittedPackages() {
+        return DeviceConfigProperties.MAX_BLOB_ACCESS_PERMITTED_PACKAGES;
+    }
+
     @Nullable
     public static File prepareBlobFile(long sessionId) {
         final File blobsDir = prepareBlobsDir();
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index a90536fe..7a6c884 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -35,6 +35,9 @@
 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT;
 import static com.android.server.blob.BlobStoreConfig.getAdjustedCommitTimeMs;
 import static com.android.server.blob.BlobStoreConfig.getDeletionOnLastLeaseDelayMs;
+import static com.android.server.blob.BlobStoreConfig.getMaxActiveSessions;
+import static com.android.server.blob.BlobStoreConfig.getMaxCommittedBlobs;
+import static com.android.server.blob.BlobStoreConfig.getMaxLeasedBlobs;
 import static com.android.server.blob.BlobStoreSession.STATE_ABANDONED;
 import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED;
 import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_INVALID;
@@ -124,6 +127,7 @@
 import java.util.Objects;
 import java.util.Random;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -332,9 +336,26 @@
         mKnownBlobIds.add(id);
     }
 
+    @GuardedBy("mBlobsLock")
+    private int getSessionsCountLocked(int uid, String packageName) {
+        // TODO: Maintain a counter instead of traversing all the sessions
+        final AtomicInteger sessionsCount = new AtomicInteger(0);
+        forEachSessionInUser(session -> {
+            if (session.getOwnerUid() == uid && session.getOwnerPackageName().equals(packageName)) {
+                sessionsCount.getAndIncrement();
+            }
+        }, UserHandle.getUserId(uid));
+        return sessionsCount.get();
+    }
+
     private long createSessionInternal(BlobHandle blobHandle,
             int callingUid, String callingPackage) {
         synchronized (mBlobsLock) {
+            final int sessionsCount = getSessionsCountLocked(callingUid, callingPackage);
+            if (sessionsCount >= getMaxActiveSessions()) {
+                throw new LimitExceededException("Too many active sessions for the caller: "
+                        + sessionsCount);
+            }
             // TODO: throw if there is already an active session associated with blobHandle.
             final long sessionId = generateNextSessionIdLocked();
             final BlobStoreSession session = new BlobStoreSession(mContext,
@@ -408,10 +429,39 @@
         }
     }
 
+    @GuardedBy("mBlobsLock")
+    private int getCommittedBlobsCountLocked(int uid, String packageName) {
+        // TODO: Maintain a counter instead of traversing all the blobs
+        final AtomicInteger blobsCount = new AtomicInteger(0);
+        forEachBlobInUser((blobMetadata) -> {
+            if (blobMetadata.isACommitter(packageName, uid)) {
+                blobsCount.getAndIncrement();
+            }
+        }, UserHandle.getUserId(uid));
+        return blobsCount.get();
+    }
+
+    @GuardedBy("mBlobsLock")
+    private int getLeasedBlobsCountLocked(int uid, String packageName) {
+        // TODO: Maintain a counter instead of traversing all the blobs
+        final AtomicInteger blobsCount = new AtomicInteger(0);
+        forEachBlobInUser((blobMetadata) -> {
+            if (blobMetadata.isALeasee(packageName, uid)) {
+                blobsCount.getAndIncrement();
+            }
+        }, UserHandle.getUserId(uid));
+        return blobsCount.get();
+    }
+
     private void acquireLeaseInternal(BlobHandle blobHandle, int descriptionResId,
             CharSequence description, long leaseExpiryTimeMillis,
             int callingUid, String callingPackage) {
         synchronized (mBlobsLock) {
+            final int leasesCount = getLeasedBlobsCountLocked(callingUid, callingPackage);
+            if (leasesCount >= getMaxLeasedBlobs()) {
+                throw new LimitExceededException("Too many leased blobs for the caller: "
+                        + leasesCount);
+            }
             final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
                     .get(blobHandle);
             if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
@@ -626,6 +676,18 @@
                 break;
             case STATE_VERIFIED_VALID:
                 synchronized (mBlobsLock) {
+                    final int committedBlobsCount = getCommittedBlobsCountLocked(
+                            session.getOwnerUid(), session.getOwnerPackageName());
+                    if (committedBlobsCount >= getMaxCommittedBlobs()) {
+                        Slog.d(TAG, "Failed to commit: too many committed blobs. count: "
+                                + committedBlobsCount + "; blob: " + session);
+                        session.sendCommitCallbackResult(COMMIT_RESULT_ERROR);
+                        session.getSessionFile().delete();
+                        mActiveBlobIds.remove(session.getSessionId());
+                        getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid()))
+                                .remove(session.getSessionId());
+                        break;
+                    }
                     final int userId = UserHandle.getUserId(session.getOwnerUid());
                     final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(
                             userId);
@@ -656,7 +718,7 @@
                         } else {
                             blob.addOrReplaceCommitter(existingCommitter);
                         }
-                        Slog.d(TAG, "Error committing the blob", e);
+                        Slog.d(TAG, "Error committing the blob: " + session, e);
                         FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED,
                                 session.getOwnerUid(), blob.getBlobId(), blob.getSize(),
                                 FrameworkStatsLog.BLOB_COMMITTED__RESULT__ERROR_DURING_COMMIT);
@@ -1096,6 +1158,16 @@
 
     void runClearAllSessions(@UserIdInt int userId) {
         synchronized (mBlobsLock) {
+            for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
+                final int sessionUserId = mSessions.keyAt(i);
+                if (userId != UserHandle.USER_ALL && userId != sessionUserId) {
+                    continue;
+                }
+                final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i);
+                for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) {
+                    mActiveBlobIds.remove(userSessions.valueAt(j).getSessionId());
+                }
+            }
             if (userId == UserHandle.USER_ALL) {
                 mSessions.clear();
             } else {
@@ -1107,6 +1179,16 @@
 
     void runClearAllBlobs(@UserIdInt int userId) {
         synchronized (mBlobsLock) {
+            for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
+                final int blobUserId = mBlobsMap.keyAt(i);
+                if (userId != UserHandle.USER_ALL && userId != blobUserId) {
+                    continue;
+                }
+                final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
+                for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
+                    mActiveBlobIds.remove(userBlobs.valueAt(j).getBlobId());
+                }
+            }
             if (userId == UserHandle.USER_ALL) {
                 mBlobsMap.clear();
             } else {
@@ -1331,8 +1413,11 @@
                         + "callingUid=" + callingUid + ", callingPackage=" + packageName);
             }
 
-            // TODO: Verify caller request is within limits (no. of calls/blob sessions/blobs)
-            return createSessionInternal(blobHandle, callingUid, packageName);
+            try {
+                return createSessionInternal(blobHandle, callingUid, packageName);
+            } catch (LimitExceededException e) {
+                throw new ParcelableException(e);
+            }
         }
 
         @Override
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
index baafff5..53e296b 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
@@ -32,6 +32,7 @@
 
 import static com.android.server.blob.BlobStoreConfig.TAG;
 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_SESSION_CREATION_TIME;
+import static com.android.server.blob.BlobStoreConfig.getMaxPermittedPackages;
 import static com.android.server.blob.BlobStoreConfig.hasSessionExpired;
 
 import android.annotation.BytesLong;
@@ -43,7 +44,9 @@
 import android.content.Context;
 import android.os.Binder;
 import android.os.FileUtils;
+import android.os.LimitExceededException;
 import android.os.ParcelFileDescriptor;
+import android.os.ParcelableException;
 import android.os.RemoteException;
 import android.os.RevocableFileDescriptor;
 import android.os.Trace;
@@ -76,7 +79,10 @@
 import java.util.Arrays;
 import java.util.Objects;
 
-/** TODO: add doc */
+/**
+ * Class to represent the state corresponding to an ongoing
+ * {@link android.app.blob.BlobStoreManager.Session}
+ */
 @VisibleForTesting
 class BlobStoreSession extends IBlobStoreSession.Stub {
 
@@ -326,6 +332,11 @@
                 throw new IllegalStateException("Not allowed to change access type in state: "
                         + stateToString(mState));
             }
+            if (mBlobAccessMode.getNumWhitelistedPackages() >= getMaxPermittedPackages()) {
+                throw new ParcelableException(new LimitExceededException(
+                        "Too many packages permitted to access the blob: "
+                                + mBlobAccessMode.getNumWhitelistedPackages()));
+            }
             mBlobAccessMode.allowPackageAccess(packageName, certificate);
         }
     }
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index 069578a..71cc044 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -880,6 +880,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 = {
@@ -1674,6 +1675,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
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/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/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/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
index 6794a2a..52d4647 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -253,7 +253,7 @@
         private final AtomicBoolean mRegistered = new AtomicBoolean();
 
         @Inject
-        public ProximityCheck(ProximitySensor sensor, DelayableExecutor delayableExecutor) {
+        public ProximityCheck(ProximitySensor sensor, @Main DelayableExecutor delayableExecutor) {
             mSensor = sensor;
             mSensor.setTag("prox_check");
             mDelayableExecutor = delayableExecutor;
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index b219e26..3a203d5 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -280,11 +280,15 @@
 
         @Override
         public void onCompatChange(String packageName) {
-            updateEnabledState(mPmInternal.getPackage(packageName));
+            AndroidPackage pkg = mPmInternal.getPackage(packageName);
+            if (pkg == null) {
+                return;
+            }
+            updateEnabledState(pkg);
             mAppsFilter.updateShouldFilterCacheForPackage(packageName);
         }
 
-        private void updateEnabledState(AndroidPackage pkg) {
+        private void updateEnabledState(@NonNull AndroidPackage pkg) {
             // TODO(b/135203078): Do not use toAppInfo
             final boolean enabled = mInjector.getCompatibility().isChangeEnabledInternal(
                     PackageManager.FILTER_APPLICATION_QUERY, pkg.toAppInfoWithoutState());
@@ -297,12 +301,12 @@
 
         @Override
         public void updatePackageState(PackageSetting setting, boolean removed) {
-            final boolean enableLogging =
+            final boolean enableLogging = setting.pkg != null &&
                     !removed && (setting.pkg.isTestOnly() || setting.pkg.isDebuggable());
             enableLogging(setting.appId, enableLogging);
             if (removed) {
-                mDisabledPackages.remove(setting.pkg.getPackageName());
-            } else {
+                mDisabledPackages.remove(setting.name);
+            } else if (setting.pkg != null) {
                 updateEnabledState(setting.pkg);
             }
         }
@@ -583,8 +587,9 @@
                 }
             }
             // if either package instruments the other, mark both as visible to one another
-            if (pkgInstruments(newPkgSetting, existingSetting)
-                    || pkgInstruments(existingSetting, newPkgSetting)) {
+            if (newPkgSetting.pkg != null && existingSetting.pkg != null
+                    && (pkgInstruments(newPkgSetting.pkg, existingSetting.pkg)
+                    || pkgInstruments(existingSetting.pkg, newPkgSetting.pkg))) {
                 mQueriesViaPackage.add(newPkgSetting.appId, existingSetting.appId);
                 mQueriesViaPackage.add(existingSetting.appId, newPkgSetting.appId);
             }
@@ -1106,16 +1111,14 @@
     }
 
     /** Returns {@code true} if the source package instruments the target package. */
-    private static boolean pkgInstruments(PackageSetting source, PackageSetting target) {
+    private static boolean pkgInstruments(
+            @NonNull AndroidPackage source, @NonNull AndroidPackage target) {
         try {
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "pkgInstruments");
-            final String packageName = target.pkg.getPackageName();
-            final List<ParsedInstrumentation> inst = source.pkg.getInstrumentations();
+            final String packageName = target.getPackageName();
+            final List<ParsedInstrumentation> inst = source.getInstrumentations();
             for (int i = ArrayUtils.size(inst) - 1; i >= 0; i--) {
                 if (Objects.equals(inst.get(i).getTargetPackage(), packageName)) {
-                    if (DEBUG_LOGGING) {
-                        log(source, target, "instrumentation");
-                    }
                     return true;
                 }
             }
diff --git a/services/core/java/com/android/server/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/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index c38d649..5f63233 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -115,7 +115,7 @@
     private static final String TAG = "UriGrantsManagerService";
     // Maximum number of persisted Uri grants a package is allowed
     private static final int MAX_PERSISTED_URI_GRANTS = 128;
-    private static final boolean ENABLE_DYNAMIC_PERMISSIONS = false;
+    private static final boolean ENABLE_DYNAMIC_PERMISSIONS = true;
 
     private final Object mLock = new Object();
     private final H mH;
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 6fbfa68..16ca60d 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -52,6 +52,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.am.PendingIntentRecord;
+import com.android.server.uri.NeededUriGrants;
 import com.android.server.wm.ActivityStackSupervisor.PendingActivityLaunch;
 import com.android.server.wm.ActivityStarter.DefaultFactory;
 import com.android.server.wm.ActivityStarter.Factory;
@@ -402,6 +403,7 @@
             // potentially acquire activity manager lock that leads to deadlock.
             for (int i = 0; i < intents.length; i++) {
                 Intent intent = intents[i];
+                NeededUriGrants intentGrants = null;
 
                 // Refuse possible leaked file descriptors.
                 if (intent.hasFileDescriptors()) {
@@ -418,6 +420,14 @@
                         0 /* startFlags */, null /* profilerInfo */, userId, filterCallingUid);
                 aInfo = mService.mAmInternal.getActivityInfoForUser(aInfo, userId);
 
+                // Carefully collect grants without holding lock
+                if (aInfo != null) {
+                    intentGrants = mSupervisor.mService.mUgmInternal
+                            .checkGrantUriPermissionFromIntent(intent, filterCallingUid,
+                                    aInfo.applicationInfo.packageName,
+                                    UserHandle.getUserId(aInfo.applicationInfo.uid));
+                }
+
                 if (aInfo != null) {
                     if ((aInfo.applicationInfo.privateFlags
                             & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
@@ -433,6 +443,7 @@
                         ? options
                         : null;
                 starters[i] = obtainStarter(intent, reason)
+                        .setIntentGrants(intentGrants)
                         .setCaller(caller)
                         .setResolvedType(resolvedTypes[i])
                         .setActivityInfo(aInfo)
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index daa97b5..25842f5 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -639,8 +639,14 @@
                         mRequest.intent, caller);
             }
 
-            // Do not lock the resolving to avoid potential deadlock.
+            // If the caller hasn't already resolved the activity, we're willing
+            // to do so here, but because that may require acquiring the AM lock
+            // as part of calculating the NeededUriGrants, we must never hold
+            // the WM lock here to avoid deadlocking.
             if (mRequest.activityInfo == null) {
+                if (Thread.holdsLock(mService.mGlobalLock)) {
+                    Slog.wtf(TAG, new IllegalStateException("Caller must not hold WM lock"));
+                }
                 mRequest.resolveActivity(mSupervisor);
             }
 
@@ -2632,6 +2638,11 @@
         return mRequest.intent;
     }
 
+    ActivityStarter setIntentGrants(NeededUriGrants intentGrants) {
+        mRequest.intentGrants = intentGrants;
+        return this;
+    }
+
     ActivityStarter setReason(String reason) {
         mRequest.reason = reason;
         return this;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index cf453c7..205523b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6174,12 +6174,10 @@
                 boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent,
                 boolean allowBackgroundActivityStart) {
             assertPackageMatchesCallingUid(callingPackage);
-            synchronized (mGlobalLock) {
-                return getActivityStartController().startActivitiesInPackage(uid, realCallingPid,
-                        realCallingUid, callingPackage, callingFeatureId, intents, resolvedTypes,
-                        resultTo, options, userId, validateIncomingUser, originatingPendingIntent,
-                        allowBackgroundActivityStart);
-            }
+            return getActivityStartController().startActivitiesInPackage(uid, realCallingPid,
+                    realCallingUid, callingPackage, callingFeatureId, intents, resolvedTypes,
+                    resultTo, options, userId, validateIncomingUser, originatingPendingIntent,
+                    allowBackgroundActivityStart);
         }
 
         @Override
@@ -6190,13 +6188,11 @@
                 boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent,
                 boolean allowBackgroundActivityStart) {
             assertPackageMatchesCallingUid(callingPackage);
-            synchronized (mGlobalLock) {
-                return getActivityStartController().startActivityInPackage(uid, realCallingPid,
-                        realCallingUid, callingPackage, callingFeatureId, intent, resolvedType,
-                        resultTo, resultWho, requestCode, startFlags, options, userId, inTask,
-                        reason, validateIncomingUser, originatingPendingIntent,
-                        allowBackgroundActivityStart);
-            }
+            return getActivityStartController().startActivityInPackage(uid, realCallingPid,
+                    realCallingUid, callingPackage, callingFeatureId, intent, resolvedType,
+                    resultTo, resultWho, requestCode, startFlags, options, userId, inTask,
+                    reason, validateIncomingUser, originatingPendingIntent,
+                    allowBackgroundActivityStart);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/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/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();
-    }
 }