Merge "Only initialize Session's package name once." into sc-dev
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
index 35cea3e..6c62426 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
@@ -26,6 +26,7 @@
 
 import java.util.Collections;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Provides results for AppSearch batch operations which encompass multiple documents.
@@ -180,7 +181,7 @@
         public Builder<KeyType, ValueType> setSuccess(
                 @NonNull KeyType key, @Nullable ValueType result) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(key);
+            Objects.requireNonNull(key);
             return setResult(key, AppSearchResult.newSuccessfulResult(result));
         }
 
@@ -198,7 +199,7 @@
                 @AppSearchResult.ResultCode int resultCode,
                 @Nullable String errorMessage) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(key);
+            Objects.requireNonNull(key);
             return setResult(key, AppSearchResult.newFailedResult(resultCode, errorMessage));
         }
 
@@ -214,8 +215,8 @@
         public Builder<KeyType, ValueType> setResult(
                 @NonNull KeyType key, @NonNull AppSearchResult<ValueType> result) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(key);
-            Preconditions.checkNotNull(result);
+            Objects.requireNonNull(key);
+            Objects.requireNonNull(result);
             if (result.isSuccess()) {
                 mSuccesses.put(key, result.getResultValue());
                 mFailures.remove(key);
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java
index b66837d..b06e215 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java
@@ -37,6 +37,8 @@
  * @param <ValueType> The type of result object for successful calls.
  */
 public final class AppSearchResult<ValueType> implements Parcelable {
+    private static final String TAG = "AppSearchResult";
+
     /**
      * Result codes from {@link AppSearchSession} methods.
      * @hide
@@ -246,14 +248,22 @@
     @NonNull
     public static <ValueType> AppSearchResult<ValueType> throwableToFailedResult(
             @NonNull Throwable t) {
-        Log.d("AppSearchResult", "Converting throwable to failed result.", t);
+        // Log for traceability. NOT_FOUND is logged at VERBOSE because this error can occur during
+        // the regular operation of the system (b/183550974). Everything else is logged at DEBUG.
+        if (t instanceof AppSearchException
+                && ((AppSearchException) t).getResultCode() == RESULT_NOT_FOUND) {
+            Log.v(TAG, "Converting throwable to failed result: " + t);
+        } else {
+            Log.d(TAG, "Converting throwable to failed result.", t);
+        }
 
         if (t instanceof AppSearchException) {
             return ((AppSearchException) t).toAppSearchResult();
         }
 
+        String exceptionClass = t.getClass().getSimpleName();
         @AppSearchResult.ResultCode int resultCode;
-        if (t instanceof IllegalStateException) {
+        if (t instanceof IllegalStateException || t instanceof NullPointerException) {
             resultCode = AppSearchResult.RESULT_INTERNAL_ERROR;
         } else if (t instanceof IllegalArgumentException) {
             resultCode = AppSearchResult.RESULT_INVALID_ARGUMENT;
@@ -262,6 +272,6 @@
         } else {
             resultCode = AppSearchResult.RESULT_UNKNOWN_ERROR;
         }
-        return AppSearchResult.newFailedResult(resultCode, t.getMessage());
+        return AppSearchResult.newFailedResult(resultCode, exceptionClass + ": " + t.getMessage());
     }
 }
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
index ac91bdb..c85c4c3 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -19,9 +19,9 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.appsearch.exceptions.AppSearchException;
 import android.app.appsearch.util.SchemaMigrationUtil;
 import android.os.Bundle;
-import android.os.ParcelableException;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.ArrayMap;
@@ -274,12 +274,14 @@
             mService.putDocuments(mPackageName, mDatabaseName, documentBundles, mUserId,
                     /*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime(),
                     new IAppSearchBatchResultCallback.Stub() {
+                        @Override
                         public void onResult(AppSearchBatchResult result) {
                             executor.execute(() -> callback.onResult(result));
                         }
 
-                        public void onSystemError(ParcelableException exception) {
-                            executor.execute(() -> callback.onSystemError(exception.getCause()));
+                        @Override
+                        public void onSystemError(AppSearchResult result) {
+                            executor.execute(() -> sendSystemErrorToCallback(result, callback));
                         }
                     });
             mIsMutated = true;
@@ -321,6 +323,7 @@
                     request.getProjectionsInternal(),
                     mUserId,
                     new IAppSearchBatchResultCallback.Stub() {
+                        @Override
                         public void onResult(AppSearchBatchResult result) {
                             executor.execute(() -> {
                                 AppSearchBatchResult.Builder<String, GenericDocument>
@@ -359,8 +362,9 @@
                             });
                         }
 
-                        public void onSystemError(ParcelableException exception) {
-                            executor.execute(() -> callback.onSystemError(exception.getCause()));
+                        @Override
+                        public void onSystemError(AppSearchResult result) {
+                            executor.execute(() -> sendSystemErrorToCallback(result, callback));
                         }
                     });
         } catch (RemoteException e) {
@@ -515,12 +519,14 @@
             mService.removeByUri(mPackageName, mDatabaseName, request.getNamespace(),
                     new ArrayList<>(request.getUris()), mUserId,
                     new IAppSearchBatchResultCallback.Stub() {
+                        @Override
                         public void onResult(AppSearchBatchResult result) {
                             executor.execute(() -> callback.onResult(result));
                         }
 
-                        public void onSystemError(ParcelableException exception) {
-                            executor.execute(() -> callback.onSystemError(exception.getCause()));
+                        @Override
+                        public void onSystemError(AppSearchResult result) {
+                            executor.execute(() -> sendSystemErrorToCallback(result, callback));
                         }
                     });
             mIsMutated = true;
@@ -817,4 +823,21 @@
             }
         });
     }
+
+    /**
+     * Calls {@link BatchResultCallback#onSystemError} with a throwable derived from the given
+     * failed {@link AppSearchResult}.
+     *
+     * <p>The {@link AppSearchResult} generally comes from
+     * {@link IAppSearchBatchResultCallback#onSystemError}.
+     *
+     * <p>This method should be called from the callback executor thread.
+     */
+    private void sendSystemErrorToCallback(
+            @NonNull AppSearchResult<?> failedResult, @NonNull BatchResultCallback<?, ?> callback) {
+        Preconditions.checkArgument(!failedResult.isSuccess());
+        Throwable throwable = new AppSearchException(
+                failedResult.getResultCode(), failedResult.getErrorMessage());
+        callback.onSystemError(throwable);
+    }
 }
diff --git a/apex/appsearch/framework/java/android/app/appsearch/BatchResultCallback.java b/apex/appsearch/framework/java/android/app/appsearch/BatchResultCallback.java
index 49049b6..28f8a7a 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/BatchResultCallback.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/BatchResultCallback.java
@@ -36,13 +36,23 @@
     void onResult(@NonNull AppSearchBatchResult<KeyType, ValueType> result);
 
     /**
-     * Called when a system error occurred.
+     * Called when a system error occurs.
      *
-     * @param throwable The cause throwable.
+     * <p>This method is only called the infrastructure is fundamentally broken or unavailable, such
+     * that none of the requests could be started. For example, it will be called if the AppSearch
+     * service unexpectedly fails to initialize and can't be recovered by any means, or if
+     * communicating to the server over Binder fails (e.g. system service crashed or device is
+     * rebooting).
+     *
+     * <p>The error is not expected to be recoverable and there is no specific recommended action
+     * other than displaying a permanent message to the user.
+     *
+     * <p>Normal errors that are caused by invalid inputs or recoverable/retriable situations
+     * are reported associated with the input that caused them via the {@link #onResult} method.
+     *
+     * @param throwable an exception describing the system error
      */
     default void onSystemError(@Nullable Throwable throwable) {
-        if (throwable != null) {
-            throw new RuntimeException(throwable);
-        }
+        throw new RuntimeException("Unrecoverable system error", throwable);
     }
 }
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl
index b1bbd18..64b331e 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl
@@ -16,10 +16,10 @@
 package android.app.appsearch;
 
 import android.app.appsearch.AppSearchBatchResult;
-import android.os.ParcelableException;
+import android.app.appsearch.AppSearchResult;
 
 /** {@hide} */
 oneway interface IAppSearchBatchResultCallback {
     void onResult(in AppSearchBatchResult result);
-    void onSystemError(in ParcelableException exception);
-}
\ No newline at end of file
+    void onSystemError(in AppSearchResult result);
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl
index 27729a5..299c9957 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl
@@ -16,9 +16,8 @@
 package android.app.appsearch;
 
 import android.app.appsearch.AppSearchResult;
-import android.os.ParcelableException;
 
 /** {@hide} */
 oneway interface IAppSearchResultCallback {
     void onResult(in AppSearchResult result);
-}
\ No newline at end of file
+}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
index a8048dc..2368bdb 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
@@ -51,7 +51,7 @@
 
     /** @hide */
     public AppSearchSchema(@NonNull Bundle bundle) {
-        Preconditions.checkNotNull(bundle);
+        Objects.requireNonNull(bundle);
         mBundle = bundle;
     }
 
@@ -125,7 +125,7 @@
 
         /** Creates a new {@link AppSearchSchema.Builder}. */
         public Builder(@NonNull String schemaType) {
-            Preconditions.checkNotNull(schemaType);
+            Objects.requireNonNull(schemaType);
             mSchemaType = schemaType;
         }
 
@@ -133,7 +133,7 @@
         @NonNull
         public AppSearchSchema.Builder addProperty(@NonNull PropertyConfig propertyConfig) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(propertyConfig);
+            Objects.requireNonNull(propertyConfig);
             String name = propertyConfig.getName();
             if (!mPropertyNames.add(name)) {
                 throw new IllegalSchemaException("Property defined more than once: " + name);
@@ -246,7 +246,7 @@
         @Nullable private Integer mHashCode;
 
         PropertyConfig(@NonNull Bundle bundle) {
-            mBundle = Preconditions.checkNotNull(bundle);
+            mBundle = Objects.requireNonNull(bundle);
         }
 
         @Override
@@ -712,7 +712,7 @@
         /** Returns the logical schema-type of the contents of this document property. */
         @NonNull
         public String getSchemaType() {
-            return Preconditions.checkNotNull(mBundle.getString(SCHEMA_TYPE_FIELD));
+            return Objects.requireNonNull(mBundle.getString(SCHEMA_TYPE_FIELD));
         }
 
         /**
@@ -755,7 +755,7 @@
             @NonNull
             public DocumentPropertyConfig.Builder setSchemaType(@NonNull String schemaType) {
                 Preconditions.checkState(!mBuilt, "Builder has already been used");
-                Preconditions.checkNotNull(schemaType);
+                Objects.requireNonNull(schemaType);
                 mBundle.putString(SCHEMA_TYPE_FIELD, schemaType);
                 return this;
             }
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
index 8c9d950..e3b3a85 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
@@ -31,6 +31,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -101,11 +102,11 @@
      * @hide
      */
     public GenericDocument(@NonNull Bundle bundle) {
-        Preconditions.checkNotNull(bundle);
+        Objects.requireNonNull(bundle);
         mBundle = bundle;
-        mProperties = Preconditions.checkNotNull(bundle.getParcelable(PROPERTIES_FIELD));
-        mUri = Preconditions.checkNotNull(mBundle.getString(URI_FIELD));
-        mSchemaType = Preconditions.checkNotNull(mBundle.getString(SCHEMA_TYPE_FIELD));
+        mProperties = Objects.requireNonNull(bundle.getParcelable(PROPERTIES_FIELD));
+        mUri = Objects.requireNonNull(mBundle.getString(URI_FIELD));
+        mSchemaType = Objects.requireNonNull(mBundle.getString(SCHEMA_TYPE_FIELD));
         mCreationTimestampMillis =
                 mBundle.getLong(CREATION_TIMESTAMP_MILLIS_FIELD, System.currentTimeMillis());
     }
@@ -199,7 +200,7 @@
      */
     @Nullable
     public Object getProperty(@NonNull String key) {
-        Preconditions.checkNotNull(key);
+        Objects.requireNonNull(key);
         Object property = mProperties.get(key);
         if (property instanceof ArrayList) {
             return getPropertyBytesArray(key);
@@ -218,7 +219,7 @@
      */
     @Nullable
     public String getPropertyString(@NonNull String key) {
-        Preconditions.checkNotNull(key);
+        Objects.requireNonNull(key);
         String[] propertyArray = getPropertyStringArray(key);
         if (propertyArray == null || propertyArray.length == 0) {
             return null;
@@ -235,7 +236,7 @@
      *     there is no such key or the value is of a different type.
      */
     public long getPropertyLong(@NonNull String key) {
-        Preconditions.checkNotNull(key);
+        Objects.requireNonNull(key);
         long[] propertyArray = getPropertyLongArray(key);
         if (propertyArray == null || propertyArray.length == 0) {
             return 0;
@@ -252,7 +253,7 @@
      *     if there is no such key or the value is of a different type.
      */
     public double getPropertyDouble(@NonNull String key) {
-        Preconditions.checkNotNull(key);
+        Objects.requireNonNull(key);
         double[] propertyArray = getPropertyDoubleArray(key);
         if (propertyArray == null || propertyArray.length == 0) {
             return 0.0;
@@ -269,7 +270,7 @@
      *     false} if there is no such key or the value is of a different type.
      */
     public boolean getPropertyBoolean(@NonNull String key) {
-        Preconditions.checkNotNull(key);
+        Objects.requireNonNull(key);
         boolean[] propertyArray = getPropertyBooleanArray(key);
         if (propertyArray == null || propertyArray.length == 0) {
             return false;
@@ -287,7 +288,7 @@
      */
     @Nullable
     public byte[] getPropertyBytes(@NonNull String key) {
-        Preconditions.checkNotNull(key);
+        Objects.requireNonNull(key);
         byte[][] propertyArray = getPropertyBytesArray(key);
         if (propertyArray == null || propertyArray.length == 0) {
             return null;
@@ -305,7 +306,7 @@
      */
     @Nullable
     public GenericDocument getPropertyDocument(@NonNull String key) {
-        Preconditions.checkNotNull(key);
+        Objects.requireNonNull(key);
         GenericDocument[] propertyArray = getPropertyDocumentArray(key);
         if (propertyArray == null || propertyArray.length == 0) {
             return null;
@@ -342,7 +343,7 @@
      */
     @Nullable
     public String[] getPropertyStringArray(@NonNull String key) {
-        Preconditions.checkNotNull(key);
+        Objects.requireNonNull(key);
         return getAndCastPropertyArray(key, String[].class);
     }
 
@@ -355,7 +356,7 @@
      */
     @Nullable
     public long[] getPropertyLongArray(@NonNull String key) {
-        Preconditions.checkNotNull(key);
+        Objects.requireNonNull(key);
         return getAndCastPropertyArray(key, long[].class);
     }
 
@@ -368,7 +369,7 @@
      */
     @Nullable
     public double[] getPropertyDoubleArray(@NonNull String key) {
-        Preconditions.checkNotNull(key);
+        Objects.requireNonNull(key);
         return getAndCastPropertyArray(key, double[].class);
     }
 
@@ -381,7 +382,7 @@
      */
     @Nullable
     public boolean[] getPropertyBooleanArray(@NonNull String key) {
-        Preconditions.checkNotNull(key);
+        Objects.requireNonNull(key);
         return getAndCastPropertyArray(key, boolean[].class);
     }
 
@@ -396,7 +397,7 @@
     @Nullable
     @SuppressWarnings("unchecked")
     public byte[][] getPropertyBytesArray(@NonNull String key) {
-        Preconditions.checkNotNull(key);
+        Objects.requireNonNull(key);
         ArrayList<Bundle> bundles = getAndCastPropertyArray(key, ArrayList.class);
         if (bundles == null || bundles.size() == 0) {
             return null;
@@ -428,7 +429,7 @@
     @SuppressLint("ArrayReturn")
     @Nullable
     public GenericDocument[] getPropertyDocumentArray(@NonNull String key) {
-        Preconditions.checkNotNull(key);
+        Objects.requireNonNull(key);
         Parcelable[] bundles = getAndCastPropertyArray(key, Parcelable[].class);
         if (bundles == null || bundles.length == 0) {
             return null;
@@ -591,9 +592,9 @@
          */
         @SuppressWarnings("unchecked")
         public Builder(@NonNull String namespace, @NonNull String uri, @NonNull String schemaType) {
-            Preconditions.checkNotNull(namespace);
-            Preconditions.checkNotNull(uri);
-            Preconditions.checkNotNull(schemaType);
+            Objects.requireNonNull(namespace);
+            Objects.requireNonNull(uri);
+            Objects.requireNonNull(schemaType);
             mBuilderTypeInstance = (BuilderType) this;
             mBundle.putString(GenericDocument.NAMESPACE_FIELD, namespace);
             mBundle.putString(GenericDocument.URI_FIELD, uri);
@@ -682,8 +683,8 @@
         @NonNull
         public BuilderType setPropertyString(@NonNull String key, @NonNull String... values) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(key);
-            Preconditions.checkNotNull(values);
+            Objects.requireNonNull(key);
+            Objects.requireNonNull(values);
             putInPropertyBundle(key, values);
             return mBuilderTypeInstance;
         }
@@ -694,15 +695,14 @@
          *
          * @param key the key associated with the {@code values}.
          * @param values the {@code boolean} values of the property.
-         * @throws IllegalArgumentException if no values are provided or if values exceed maximum
-         *     repeated property length.
+         * @throws IllegalArgumentException if values exceed maximum repeated property length.
          * @throws IllegalStateException if the builder has already been used.
          */
         @NonNull
         public BuilderType setPropertyBoolean(@NonNull String key, @NonNull boolean... values) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(key);
-            Preconditions.checkNotNull(values);
+            Objects.requireNonNull(key);
+            Objects.requireNonNull(values);
             putInPropertyBundle(key, values);
             return mBuilderTypeInstance;
         }
@@ -712,15 +712,14 @@
          *
          * @param key the key associated with the {@code values}.
          * @param values the {@code long} values of the property.
-         * @throws IllegalArgumentException if no values are provided or if values exceed maximum
-         *     repeated property length.
+         * @throws IllegalArgumentException if values exceed maximum repeated property length.
          * @throws IllegalStateException if the builder has already been used.
          */
         @NonNull
         public BuilderType setPropertyLong(@NonNull String key, @NonNull long... values) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(key);
-            Preconditions.checkNotNull(values);
+            Objects.requireNonNull(key);
+            Objects.requireNonNull(values);
             putInPropertyBundle(key, values);
             return mBuilderTypeInstance;
         }
@@ -730,15 +729,14 @@
          *
          * @param key the key associated with the {@code values}.
          * @param values the {@code double} values of the property.
-         * @throws IllegalArgumentException if no values are provided or if values exceed maximum
-         *     repeated property length.
+         * @throws IllegalArgumentException if values exceed maximum repeated property length.
          * @throws IllegalStateException if the builder has already been used.
          */
         @NonNull
         public BuilderType setPropertyDouble(@NonNull String key, @NonNull double... values) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(key);
-            Preconditions.checkNotNull(values);
+            Objects.requireNonNull(key);
+            Objects.requireNonNull(values);
             putInPropertyBundle(key, values);
             return mBuilderTypeInstance;
         }
@@ -755,8 +753,8 @@
         @NonNull
         public BuilderType setPropertyBytes(@NonNull String key, @NonNull byte[]... values) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(key);
-            Preconditions.checkNotNull(values);
+            Objects.requireNonNull(key);
+            Objects.requireNonNull(values);
             putInPropertyBundle(key, values);
             return mBuilderTypeInstance;
         }
@@ -776,8 +774,8 @@
         public BuilderType setPropertyDocument(
                 @NonNull String key, @NonNull GenericDocument... values) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(key);
-            Preconditions.checkNotNull(values);
+            Objects.requireNonNull(key);
+            Objects.requireNonNull(values);
             putInPropertyBundle(key, values);
             return mBuilderTypeInstance;
         }
@@ -850,9 +848,7 @@
         }
 
         private static void validateRepeatedPropertyLength(@NonNull String key, int length) {
-            if (length == 0) {
-                throw new IllegalArgumentException("The input array is empty.");
-            } else if (length > MAX_REPEATED_PROPERTY_LENGTH) {
+            if (length > MAX_REPEATED_PROPERTY_LENGTH) {
                 throw new IllegalArgumentException(
                         "Repeated property \""
                                 + key
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
index 1719e14..4dc3225 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
@@ -28,6 +28,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -52,9 +53,9 @@
             @NonNull String namespace,
             @NonNull Set<String> uris,
             @NonNull Map<String, List<String>> typePropertyPathsMap) {
-        mNamespace = Preconditions.checkNotNull(namespace);
-        mUris = Preconditions.checkNotNull(uris);
-        mTypePropertyPathsMap = Preconditions.checkNotNull(typePropertyPathsMap);
+        mNamespace = Objects.requireNonNull(namespace);
+        mUris = Objects.requireNonNull(uris);
+        mTypePropertyPathsMap = Objects.requireNonNull(typePropertyPathsMap);
     }
 
     /** Returns the namespace attached to the request. */
@@ -114,7 +115,7 @@
 
         /** Creates a {@link GetByUriRequest.Builder} instance. */
         public Builder(@NonNull String namespace) {
-            mNamespace = Preconditions.checkNotNull(namespace);
+            mNamespace = Objects.requireNonNull(namespace);
         }
 
         /**
@@ -124,7 +125,7 @@
          */
         @NonNull
         public Builder addUris(@NonNull String... uris) {
-            Preconditions.checkNotNull(uris);
+            Objects.requireNonNull(uris);
             return addUris(Arrays.asList(uris));
         }
 
@@ -136,7 +137,7 @@
         @NonNull
         public Builder addUris(@NonNull Collection<String> uris) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(uris);
+            Objects.requireNonNull(uris);
             mUris.addAll(uris);
             return this;
         }
@@ -161,11 +162,11 @@
         public Builder addProjection(
                 @NonNull String schemaType, @NonNull Collection<String> propertyPaths) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(schemaType);
-            Preconditions.checkNotNull(propertyPaths);
+            Objects.requireNonNull(schemaType);
+            Objects.requireNonNull(propertyPaths);
             List<String> propertyPathsList = new ArrayList<>(propertyPaths.size());
             for (String propertyPath : propertyPaths) {
-                Preconditions.checkNotNull(propertyPath);
+                Objects.requireNonNull(propertyPath);
                 propertyPathsList.add(propertyPath);
             }
             mProjectionTypePropertyPaths.put(schemaType, propertyPathsList);
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java b/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java
index 1f56ef3..691ef4f 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java
@@ -24,6 +24,7 @@
 import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
+import java.util.Objects;
 import java.util.Set;
 
 /** The response class of {@link AppSearchSession#getSchema} */
@@ -34,7 +35,7 @@
     private final Bundle mBundle;
 
     GetSchemaResponse(@NonNull Bundle bundle) {
-        mBundle = Preconditions.checkNotNull(bundle);
+        mBundle = Objects.requireNonNull(bundle);
     }
 
     /**
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/PackageIdentifier.java b/apex/appsearch/framework/java/external/android/app/appsearch/PackageIdentifier.java
index bfb9323..4f63bae 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/PackageIdentifier.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/PackageIdentifier.java
@@ -20,7 +20,7 @@
 import android.app.appsearch.util.BundleUtil;
 import android.os.Bundle;
 
-import com.android.internal.util.Preconditions;
+import java.util.Objects;
 
 /** This class represents a uniquely identifiable package. */
 public class PackageIdentifier {
@@ -43,7 +43,7 @@
 
     /** @hide */
     public PackageIdentifier(@NonNull Bundle bundle) {
-        mBundle = Preconditions.checkNotNull(bundle);
+        mBundle = Objects.requireNonNull(bundle);
     }
 
     /** @hide */
@@ -54,12 +54,12 @@
 
     @NonNull
     public String getPackageName() {
-        return Preconditions.checkNotNull(mBundle.getString(PACKAGE_NAME_FIELD));
+        return Objects.requireNonNull(mBundle.getString(PACKAGE_NAME_FIELD));
     }
 
     @NonNull
     public byte[] getSha256Certificate() {
-        return Preconditions.checkNotNull(mBundle.getByteArray(SHA256_CERTIFICATE_FIELD));
+        return Objects.requireNonNull(mBundle.getByteArray(SHA256_CERTIFICATE_FIELD));
     }
 
     @Override
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java
index 01473be..b49e0e8 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java
@@ -26,6 +26,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Encapsulates a request to index documents into an {@link AppSearchSession} database.
@@ -61,7 +62,7 @@
          */
         @NonNull
         public Builder addGenericDocuments(@NonNull GenericDocument... documents) {
-            Preconditions.checkNotNull(documents);
+            Objects.requireNonNull(documents);
             return addGenericDocuments(Arrays.asList(documents));
         }
 
@@ -74,7 +75,7 @@
         public Builder addGenericDocuments(
                 @NonNull Collection<? extends GenericDocument> documents) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(documents);
+            Objects.requireNonNull(documents);
             mDocuments.addAll(documents);
             return this;
         }
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
index 8da68c0..4dcad68 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
@@ -24,6 +24,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -65,7 +66,7 @@
 
         /** Creates a {@link RemoveByUriRequest.Builder} instance. */
         public Builder(@NonNull String namespace) {
-            mNamespace = Preconditions.checkNotNull(namespace);
+            mNamespace = Objects.requireNonNull(namespace);
         }
 
         /**
@@ -75,7 +76,7 @@
          */
         @NonNull
         public Builder addUris(@NonNull String... uris) {
-            Preconditions.checkNotNull(uris);
+            Objects.requireNonNull(uris);
             return addUris(Arrays.asList(uris));
         }
 
@@ -87,7 +88,7 @@
         @NonNull
         public Builder addUris(@NonNull Collection<String> uris) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(uris);
+            Objects.requireNonNull(uris);
             mUris.addAll(uris);
             return this;
         }
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/ReportSystemUsageRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/ReportSystemUsageRequest.java
index 2e152f8..8aff3b4 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/ReportSystemUsageRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/ReportSystemUsageRequest.java
@@ -20,6 +20,8 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.util.Objects;
+
 /**
  * A request to report usage of a document owned by another app from a system UI surface.
  *
@@ -42,10 +44,10 @@
             @NonNull String namespace,
             @NonNull String uri,
             long usageTimeMillis) {
-        mPackageName = Preconditions.checkNotNull(packageName);
-        mDatabase = Preconditions.checkNotNull(database);
-        mNamespace = Preconditions.checkNotNull(namespace);
-        mUri = Preconditions.checkNotNull(uri);
+        mPackageName = Objects.requireNonNull(packageName);
+        mDatabase = Objects.requireNonNull(database);
+        mNamespace = Objects.requireNonNull(namespace);
+        mUri = Objects.requireNonNull(uri);
         mUsageTimeMillis = usageTimeMillis;
     }
 
@@ -95,9 +97,9 @@
         /** Creates a {@link ReportSystemUsageRequest.Builder} instance. */
         public Builder(
                 @NonNull String packageName, @NonNull String database, @NonNull String namespace) {
-            mPackageName = Preconditions.checkNotNull(packageName);
-            mDatabase = Preconditions.checkNotNull(database);
-            mNamespace = Preconditions.checkNotNull(namespace);
+            mPackageName = Objects.requireNonNull(packageName);
+            mDatabase = Objects.requireNonNull(database);
+            mNamespace = Objects.requireNonNull(namespace);
         }
 
         /**
@@ -110,7 +112,7 @@
         @NonNull
         public ReportSystemUsageRequest.Builder setUri(@NonNull String uri) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(uri);
+            Objects.requireNonNull(uri);
             mUri = uri;
             return this;
         }
@@ -142,7 +144,7 @@
         @NonNull
         public ReportSystemUsageRequest build() {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(mUri, "ReportUsageRequest is missing a URI");
+            Objects.requireNonNull(mUri, "ReportUsageRequest is missing a URI");
             if (mUsageTimeMillis == null) {
                 mUsageTimeMillis = System.currentTimeMillis();
             }
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java
index 646e73c..925bde9 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java
@@ -20,6 +20,8 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.util.Objects;
+
 /**
  * A request to report usage of a document.
  *
@@ -33,8 +35,8 @@
     private final long mUsageTimeMillis;
 
     ReportUsageRequest(@NonNull String namespace, @NonNull String uri, long usageTimeMillis) {
-        mNamespace = Preconditions.checkNotNull(namespace);
-        mUri = Preconditions.checkNotNull(uri);
+        mNamespace = Objects.requireNonNull(namespace);
+        mUri = Objects.requireNonNull(uri);
         mUsageTimeMillis = usageTimeMillis;
     }
 
@@ -69,7 +71,7 @@
 
         /** Creates a {@link ReportUsageRequest.Builder} instance. */
         public Builder(@NonNull String namespace) {
-            mNamespace = Preconditions.checkNotNull(namespace);
+            mNamespace = Objects.requireNonNull(namespace);
         }
 
         /**
@@ -82,7 +84,7 @@
         @NonNull
         public ReportUsageRequest.Builder setUri(@NonNull String uri) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(uri);
+            Objects.requireNonNull(uri);
             mUri = uri;
             return this;
         }
@@ -114,7 +116,7 @@
         @NonNull
         public ReportUsageRequest build() {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(mUri, "ReportUsageRequest is missing a URI");
+            Objects.requireNonNull(mUri, "ReportUsageRequest is missing a URI");
             if (mUsageTimeMillis == null) {
                 mUsageTimeMillis = System.currentTimeMillis();
             }
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
index 55a228d..432f838 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
@@ -59,7 +59,7 @@
 
     /** @hide */
     public SearchResult(@NonNull Bundle bundle) {
-        mBundle = Preconditions.checkNotNull(bundle);
+        mBundle = Objects.requireNonNull(bundle);
     }
 
     /** @hide */
@@ -77,8 +77,7 @@
     public GenericDocument getGenericDocument() {
         if (mDocument == null) {
             mDocument =
-                    new GenericDocument(
-                            Preconditions.checkNotNull(mBundle.getBundle(DOCUMENT_FIELD)));
+                    new GenericDocument(Objects.requireNonNull(mBundle.getBundle(DOCUMENT_FIELD)));
         }
         return mDocument;
     }
@@ -95,7 +94,7 @@
     public List<MatchInfo> getMatches() {
         if (mMatches == null) {
             List<Bundle> matchBundles =
-                    Preconditions.checkNotNull(mBundle.getParcelableArrayList(MATCHES_FIELD));
+                    Objects.requireNonNull(mBundle.getParcelableArrayList(MATCHES_FIELD));
             mMatches = new ArrayList<>(matchBundles.size());
             for (int i = 0; i < matchBundles.size(); i++) {
                 MatchInfo matchInfo = new MatchInfo(matchBundles.get(i), getGenericDocument());
@@ -112,7 +111,7 @@
      */
     @NonNull
     public String getPackageName() {
-        return Preconditions.checkNotNull(mBundle.getString(PACKAGE_NAME_FIELD));
+        return Objects.requireNonNull(mBundle.getString(PACKAGE_NAME_FIELD));
     }
 
     /**
@@ -122,7 +121,7 @@
      */
     @NonNull
     public String getDatabaseName() {
-        return Preconditions.checkNotNull(mBundle.getString(DATABASE_NAME_FIELD));
+        return Objects.requireNonNull(mBundle.getString(DATABASE_NAME_FIELD));
     }
 
     /**
@@ -169,8 +168,8 @@
          * @param databaseName the database name the matched document belongs to.
          */
         public Builder(@NonNull String packageName, @NonNull String databaseName) {
-            mBundle.putString(PACKAGE_NAME_FIELD, Preconditions.checkNotNull(packageName));
-            mBundle.putString(DATABASE_NAME_FIELD, Preconditions.checkNotNull(databaseName));
+            mBundle.putString(PACKAGE_NAME_FIELD, Objects.requireNonNull(packageName));
+            mBundle.putString(DATABASE_NAME_FIELD, Objects.requireNonNull(databaseName));
         }
 
         /**
@@ -312,9 +311,9 @@
         @Nullable private MatchRange mWindowRange;
 
         MatchInfo(@NonNull Bundle bundle, @Nullable GenericDocument document) {
-            mBundle = Preconditions.checkNotNull(bundle);
+            mBundle = Objects.requireNonNull(bundle);
             mDocument = document;
-            mPropertyPath = Preconditions.checkNotNull(bundle.getString(PROPERTY_PATH_FIELD));
+            mPropertyPath = Objects.requireNonNull(bundle.getString(PROPERTY_PATH_FIELD));
         }
 
         /**
@@ -449,7 +448,7 @@
                 Preconditions.checkState(!mBuilt, "Builder has already been used");
                 mBundle.putString(
                         SearchResult.MatchInfo.PROPERTY_PATH_FIELD,
-                        Preconditions.checkNotNull(propertyPath));
+                        Objects.requireNonNull(propertyPath));
                 return this;
             }
 
@@ -461,7 +460,7 @@
             @NonNull
             public Builder setExactMatchRange(@NonNull MatchRange matchRange) {
                 Preconditions.checkState(!mBuilt, "Builder has already been used");
-                Preconditions.checkNotNull(matchRange);
+                Objects.requireNonNull(matchRange);
                 mBundle.putInt(MatchInfo.EXACT_MATCH_RANGE_LOWER_FIELD, matchRange.getStart());
                 mBundle.putInt(MatchInfo.EXACT_MATCH_RANGE_UPPER_FIELD, matchRange.getEnd());
                 return this;
@@ -475,7 +474,7 @@
             @NonNull
             public Builder setSnippetRange(@NonNull MatchRange matchRange) {
                 Preconditions.checkState(!mBuilt, "Builder has already been used");
-                Preconditions.checkNotNull(matchRange);
+                Objects.requireNonNull(matchRange);
                 mBundle.putInt(MatchInfo.SNIPPET_RANGE_LOWER_FIELD, matchRange.getStart());
                 mBundle.putInt(MatchInfo.SNIPPET_RANGE_UPPER_FIELD, matchRange.getEnd());
                 return this;
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResultPage.java b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResultPage.java
index dbd09d6..4853b5b 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResultPage.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResultPage.java
@@ -20,11 +20,10 @@
 import android.annotation.Nullable;
 import android.os.Bundle;
 
-import com.android.internal.util.Preconditions;
-
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * This class represents a page of {@link SearchResult}s
@@ -41,7 +40,7 @@
     @NonNull private final Bundle mBundle;
 
     public SearchResultPage(@NonNull Bundle bundle) {
-        mBundle = Preconditions.checkNotNull(bundle);
+        mBundle = Objects.requireNonNull(bundle);
         mNextPageToken = mBundle.getLong(NEXT_PAGE_TOKEN_FIELD);
     }
 
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
index 19d9430..d466bf1 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
@@ -34,6 +34,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -176,7 +177,7 @@
 
     /** @hide */
     public SearchSpec(@NonNull Bundle bundle) {
-        Preconditions.checkNotNull(bundle);
+        Objects.requireNonNull(bundle);
         mBundle = bundle;
     }
 
@@ -342,7 +343,7 @@
          */
         @NonNull
         public Builder addFilterSchemas(@NonNull String... schemas) {
-            Preconditions.checkNotNull(schemas);
+            Objects.requireNonNull(schemas);
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             return addFilterSchemas(Arrays.asList(schemas));
         }
@@ -355,7 +356,7 @@
          */
         @NonNull
         public Builder addFilterSchemas(@NonNull Collection<String> schemas) {
-            Preconditions.checkNotNull(schemas);
+            Objects.requireNonNull(schemas);
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             mSchemas.addAll(schemas);
             return this;
@@ -369,7 +370,7 @@
          */
         @NonNull
         public Builder addFilterNamespaces(@NonNull String... namespaces) {
-            Preconditions.checkNotNull(namespaces);
+            Objects.requireNonNull(namespaces);
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             return addFilterNamespaces(Arrays.asList(namespaces));
         }
@@ -382,7 +383,7 @@
          */
         @NonNull
         public Builder addFilterNamespaces(@NonNull Collection<String> namespaces) {
-            Preconditions.checkNotNull(namespaces);
+            Objects.requireNonNull(namespaces);
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             mNamespaces.addAll(namespaces);
             return this;
@@ -398,7 +399,7 @@
          */
         @NonNull
         public Builder addFilterPackageNames(@NonNull String... packageNames) {
-            Preconditions.checkNotNull(packageNames);
+            Objects.requireNonNull(packageNames);
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             return addFilterPackageNames(Arrays.asList(packageNames));
         }
@@ -413,7 +414,7 @@
          */
         @NonNull
         public Builder addFilterPackageNames(@NonNull Collection<String> packageNames) {
-            Preconditions.checkNotNull(packageNames);
+            Objects.requireNonNull(packageNames);
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             mPackageNames.addAll(packageNames);
             return this;
@@ -586,11 +587,11 @@
         public SearchSpec.Builder addProjection(
                 @NonNull String schema, @NonNull Collection<String> propertyPaths) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(schema);
-            Preconditions.checkNotNull(propertyPaths);
+            Objects.requireNonNull(schema);
+            Objects.requireNonNull(propertyPaths);
             ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size());
             for (String propertyPath : propertyPaths) {
-                Preconditions.checkNotNull(propertyPath);
+                Objects.requireNonNull(propertyPath);
                 propertyPathsArrayList.add(propertyPath);
             }
             mProjectionTypePropertyMasks.putStringArrayList(schema, propertyPathsArrayList);
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
index 5672bc7..8f7a0bf 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
@@ -28,6 +28,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -94,10 +95,10 @@
             @NonNull Map<String, Migrator> migrators,
             boolean forceOverride,
             int version) {
-        mSchemas = Preconditions.checkNotNull(schemas);
-        mSchemasNotDisplayedBySystem = Preconditions.checkNotNull(schemasNotDisplayedBySystem);
-        mSchemasVisibleToPackages = Preconditions.checkNotNull(schemasVisibleToPackages);
-        mMigrators = Preconditions.checkNotNull(migrators);
+        mSchemas = Objects.requireNonNull(schemas);
+        mSchemasNotDisplayedBySystem = Objects.requireNonNull(schemasNotDisplayedBySystem);
+        mSchemasVisibleToPackages = Objects.requireNonNull(schemasVisibleToPackages);
+        mMigrators = Objects.requireNonNull(migrators);
         mForceOverride = forceOverride;
         mVersion = version;
     }
@@ -192,7 +193,7 @@
          */
         @NonNull
         public Builder addSchemas(@NonNull AppSearchSchema... schemas) {
-            Preconditions.checkNotNull(schemas);
+            Objects.requireNonNull(schemas);
             return addSchemas(Arrays.asList(schemas));
         }
 
@@ -206,7 +207,7 @@
         @NonNull
         public Builder addSchemas(@NonNull Collection<AppSearchSchema> schemas) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(schemas);
+            Objects.requireNonNull(schemas);
             mSchemas.addAll(schemas);
             return this;
         }
@@ -231,7 +232,7 @@
         @NonNull
         public Builder setSchemaTypeDisplayedBySystem(
                 @NonNull String schemaType, boolean displayed) {
-            Preconditions.checkNotNull(schemaType);
+            Objects.requireNonNull(schemaType);
             Preconditions.checkState(!mBuilt, "Builder has already been used");
 
             if (displayed) {
@@ -270,8 +271,8 @@
                 @NonNull String schemaType,
                 boolean visible,
                 @NonNull PackageIdentifier packageIdentifier) {
-            Preconditions.checkNotNull(schemaType);
-            Preconditions.checkNotNull(packageIdentifier);
+            Objects.requireNonNull(schemaType);
+            Objects.requireNonNull(packageIdentifier);
             Preconditions.checkState(!mBuilt, "Builder has already been used");
 
             Set<PackageIdentifier> packageIdentifiers = mSchemasVisibleToPackages.get(schemaType);
@@ -321,8 +322,8 @@
         @NonNull
         @SuppressLint("MissingGetterMatchingBuilder") // Getter return plural objects.
         public Builder setMigrator(@NonNull String schemaType, @NonNull Migrator migrator) {
-            Preconditions.checkNotNull(schemaType);
-            Preconditions.checkNotNull(migrator);
+            Objects.requireNonNull(schemaType);
+            Objects.requireNonNull(migrator);
             mMigrators.put(schemaType, migrator);
             return this;
         }
@@ -350,7 +351,7 @@
          */
         @NonNull
         public Builder setMigrators(@NonNull Map<String, Migrator> migrators) {
-            Preconditions.checkNotNull(migrators);
+            Objects.requireNonNull(migrators);
             mMigrators.putAll(migrators);
             return this;
         }
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
index d63e437..7be589f 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
@@ -27,6 +27,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 /** The response class of {@link AppSearchSession#setSchema} */
@@ -61,8 +62,8 @@
     @Nullable private Set<String> mIncompatibleTypes;
 
     SetSchemaResponse(@NonNull Bundle bundle, @NonNull List<MigrationFailure> migrationFailures) {
-        mBundle = Preconditions.checkNotNull(bundle);
-        mMigrationFailures = Preconditions.checkNotNull(migrationFailures);
+        mBundle = Objects.requireNonNull(bundle);
+        mMigrationFailures = Objects.requireNonNull(migrationFailures);
     }
 
     SetSchemaResponse(@NonNull Bundle bundle) {
@@ -103,7 +104,7 @@
         if (mDeletedTypes == null) {
             mDeletedTypes =
                     new ArraySet<>(
-                            Preconditions.checkNotNull(
+                            Objects.requireNonNull(
                                     mBundle.getStringArrayList(DELETED_TYPES_FIELD)));
         }
         return Collections.unmodifiableSet(mDeletedTypes);
@@ -118,7 +119,7 @@
         if (mMigratedTypes == null) {
             mMigratedTypes =
                     new ArraySet<>(
-                            Preconditions.checkNotNull(
+                            Objects.requireNonNull(
                                     mBundle.getStringArrayList(MIGRATED_TYPES_FIELD)));
         }
         return Collections.unmodifiableSet(mMigratedTypes);
@@ -139,7 +140,7 @@
         if (mIncompatibleTypes == null) {
             mIncompatibleTypes =
                     new ArraySet<>(
-                            Preconditions.checkNotNull(
+                            Objects.requireNonNull(
                                     mBundle.getStringArrayList(INCOMPATIBLE_TYPES_FIELD)));
         }
         return Collections.unmodifiableSet(mIncompatibleTypes);
@@ -173,7 +174,7 @@
         public Builder addMigrationFailures(
                 @NonNull Collection<MigrationFailure> migrationFailures) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mMigrationFailures.addAll(Preconditions.checkNotNull(migrationFailures));
+            mMigrationFailures.addAll(Objects.requireNonNull(migrationFailures));
             return this;
         }
 
@@ -181,7 +182,7 @@
         @NonNull
         public Builder addMigrationFailure(@NonNull MigrationFailure migrationFailure) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mMigrationFailures.add(Preconditions.checkNotNull(migrationFailure));
+            mMigrationFailures.add(Objects.requireNonNull(migrationFailure));
             return this;
         }
 
@@ -189,7 +190,7 @@
         @NonNull
         public Builder addDeletedTypes(@NonNull Collection<String> deletedTypes) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mDeletedTypes.addAll(Preconditions.checkNotNull(deletedTypes));
+            mDeletedTypes.addAll(Objects.requireNonNull(deletedTypes));
             return this;
         }
 
@@ -197,7 +198,7 @@
         @NonNull
         public Builder addDeletedType(@NonNull String deletedType) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mDeletedTypes.add(Preconditions.checkNotNull(deletedType));
+            mDeletedTypes.add(Objects.requireNonNull(deletedType));
             return this;
         }
 
@@ -205,7 +206,7 @@
         @NonNull
         public Builder addIncompatibleTypes(@NonNull Collection<String> incompatibleTypes) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mIncompatibleTypes.addAll(Preconditions.checkNotNull(incompatibleTypes));
+            mIncompatibleTypes.addAll(Objects.requireNonNull(incompatibleTypes));
             return this;
         }
 
@@ -213,7 +214,7 @@
         @NonNull
         public Builder addIncompatibleType(@NonNull String incompatibleType) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mIncompatibleTypes.add(Preconditions.checkNotNull(incompatibleType));
+            mIncompatibleTypes.add(Objects.requireNonNull(incompatibleType));
             return this;
         }
 
@@ -221,7 +222,7 @@
         @NonNull
         public Builder addMigratedTypes(@NonNull Collection<String> migratedTypes) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mMigratedTypes.addAll(Preconditions.checkNotNull(migratedTypes));
+            mMigratedTypes.addAll(Objects.requireNonNull(migratedTypes));
             return this;
         }
 
@@ -229,7 +230,7 @@
         @NonNull
         public Builder addMigratedType(@NonNull String migratedType) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mMigratedTypes.add(Preconditions.checkNotNull(migratedType));
+            mMigratedTypes.add(Objects.requireNonNull(migratedType));
             return this;
         }
 
@@ -318,7 +319,7 @@
             @NonNull
             public Builder setSchemaType(@NonNull String schemaType) {
                 Preconditions.checkState(!mBuilt, "Builder has already been used");
-                mSchemaType = Preconditions.checkNotNull(schemaType);
+                mSchemaType = Objects.requireNonNull(schemaType);
                 return this;
             }
 
@@ -326,7 +327,7 @@
             @NonNull
             public Builder setNamespace(@NonNull String namespace) {
                 Preconditions.checkState(!mBuilt, "Builder has already been used");
-                mNamespace = Preconditions.checkNotNull(namespace);
+                mNamespace = Objects.requireNonNull(namespace);
                 return this;
             }
 
@@ -334,7 +335,7 @@
             @NonNull
             public Builder setUri(@NonNull String uri) {
                 Preconditions.checkState(!mBuilt, "Builder has already been used");
-                mUri = Preconditions.checkNotNull(uri);
+                mUri = Objects.requireNonNull(uri);
                 return this;
             }
 
@@ -343,7 +344,7 @@
             public Builder setAppSearchResult(@NonNull AppSearchResult<Void> appSearchResult) {
                 Preconditions.checkState(!mBuilt, "Builder has already been used");
                 Preconditions.checkState(!appSearchResult.isSuccess(), "Input a success result");
-                mFailureResult = Preconditions.checkNotNull(appSearchResult);
+                mFailureResult = Objects.requireNonNull(appSearchResult);
                 return this;
             }
 
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/StorageInfo.java b/apex/appsearch/framework/java/external/android/app/appsearch/StorageInfo.java
index dc04cf3..502b939 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/StorageInfo.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/StorageInfo.java
@@ -21,6 +21,8 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.util.Objects;
+
 /** The response class of {@code AppSearchSession#getStorageInfo}. */
 public class StorageInfo {
 
@@ -31,7 +33,7 @@
     private final Bundle mBundle;
 
     StorageInfo(@NonNull Bundle bundle) {
-        mBundle = Preconditions.checkNotNull(bundle);
+        mBundle = Objects.requireNonNull(bundle);
     }
 
     /**
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java b/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java
index 32d7e043..10e014b 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java
@@ -37,7 +37,11 @@
 public final class SchemaMigrationUtil {
     private SchemaMigrationUtil() {}
 
-    /** Returns all active {@link Migrator}s that need to be triggered in this migration. */
+    /**
+     * Returns all active {@link Migrator}s that need to be triggered in this migration.
+     *
+     * <p>{@link Migrator#shouldMigrate} returns {@code true} will make the {@link Migrator} active.
+     */
     @NonNull
     public static Map<String, Migrator> getActiveMigrators(
             @NonNull Set<AppSearchSchema> existingSchemas,
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index 509877e..f6f5c98 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -16,7 +16,6 @@
 package com.android.server.appsearch;
 
 import static android.app.appsearch.AppSearchResult.throwableToFailedResult;
-import static android.os.Process.INVALID_UID;
 import static android.os.UserHandle.USER_NULL;
 
 import android.annotation.ElapsedRealtimeLong;
@@ -37,7 +36,6 @@
 import android.app.appsearch.SearchSpec;
 import android.app.appsearch.SetSchemaResponse;
 import android.app.appsearch.StorageInfo;
-import android.app.appsearch.exceptions.AppSearchException;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -46,7 +44,6 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
-import android.os.ParcelableException;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -54,9 +51,9 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.appsearch.external.localstorage.AppSearchImpl;
@@ -64,6 +61,8 @@
 import com.android.server.appsearch.stats.LoggerInstanceManager;
 import com.android.server.appsearch.stats.PlatformLogger;
 
+import com.google.android.icing.proto.PersistType;
+
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.EOFException;
@@ -75,7 +74,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
-import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
@@ -94,7 +93,7 @@
     // mutate requests will need to gain write lock and query requests need to gain read lock.
     private static final Executor EXECUTOR = new ThreadPoolExecutor(/*corePoolSize=*/1,
             Runtime.getRuntime().availableProcessors(), /*keepAliveTime*/ 60L, TimeUnit.SECONDS,
-            new SynchronousQueue<Runnable>());
+            new LinkedBlockingQueue<>());
 
     // Cache of unlocked user ids so we don't have to query UserManager service each time. The
     // "locked" suffix refers to the fact that access to the field should be locked; unrelated to
@@ -121,14 +120,6 @@
         mContext.registerReceiverAsUser(new UserActionReceiver(), UserHandle.ALL,
                 new IntentFilter(Intent.ACTION_USER_REMOVED), /*broadcastPermission=*/ null,
                 /*scheduler=*/ null);
-
-        IntentFilter packageChangedFilter = new IntentFilter();
-        packageChangedFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
-        packageChangedFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
-        packageChangedFilter.addDataScheme("package");
-        mContext.registerReceiverAsUser(new PackageChangedReceiver(), UserHandle.ALL,
-                packageChangedFilter, /*broadcastPermission=*/ null,
-                /*scheduler=*/ null);
     }
 
     private class UserActionReceiver extends BroadcastReceiver {
@@ -136,15 +127,15 @@
         public void onReceive(@NonNull Context context, @NonNull Intent intent) {
             switch (intent.getAction()) {
                 case Intent.ACTION_USER_REMOVED:
-                    int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
+                    final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
                     if (userId == USER_NULL) {
-                        Log.e(TAG, "userId is missing in the intent: " + intent);
+                        Slog.e(TAG, "userId is missing in the intent: " + intent);
                         return;
                     }
                     handleUserRemoved(userId);
                     break;
                 default:
-                    Log.e(TAG, "Received unknown intent: " + intent);
+                    Slog.e(TAG, "Received unknown intent: " + intent);
             }
         }
     }
@@ -164,44 +155,9 @@
         try {
             mImplInstanceManager.removeAppSearchImplForUser(userId);
             mLoggerInstanceManager.removePlatformLoggerForUser(userId);
-            Log.i(TAG, "Removed AppSearchImpl instance for user: " + userId);
+            Slog.i(TAG, "Removed AppSearchImpl instance for user: " + userId);
         } catch (Throwable t) {
-            Log.e(TAG, "Unable to remove data for user: " + userId, t);
-        }
-    }
-
-    private class PackageChangedReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(@NonNull Context context, @NonNull Intent intent) {
-            switch (intent.getAction()) {
-                case Intent.ACTION_PACKAGE_FULLY_REMOVED:
-                case Intent.ACTION_PACKAGE_DATA_CLEARED:
-                    String packageName = intent.getData().getSchemeSpecificPart();
-                    if (packageName == null) {
-                        Log.e(TAG, "Package name is missing in the intent: " + intent);
-                        return;
-                    }
-                    int uid = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID);
-                    if (uid == INVALID_UID) {
-                        Log.e(TAG, "uid is missing in the intent: " + intent);
-                        return;
-                    }
-                    handlePackageRemoved(packageName, uid);
-                    break;
-                default:
-                    Log.e(TAG, "Received unknown intent: " + intent);
-            }
-        }
-    }
-
-    private void handlePackageRemoved(String packageName, int uid) {
-        int userId = UserHandle.getUserId(uid);
-        try {
-            AppSearchImpl impl = mImplInstanceManager.getOrCreateAppSearchImpl(mContext, userId);
-            //TODO(b/145759910) clear visibility setting for package.
-            impl.clearPackageData(packageName);
-        } catch (AppSearchException e) {
-            Log.e(TAG, "Unable to remove data for package: " + packageName, e);
+            Slog.e(TAG, "Unable to remove data for user: " + userId, t);
         }
     }
 
@@ -224,10 +180,10 @@
                 int schemaVersion,
                 @UserIdInt int userId,
                 @NonNull IAppSearchResultCallback callback) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(databaseName);
-            Preconditions.checkNotNull(schemaBundles);
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(databaseName);
+            Objects.requireNonNull(schemaBundles);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -273,9 +229,9 @@
                 @NonNull String databaseName,
                 @UserIdInt int userId,
                 @NonNull IAppSearchResultCallback callback) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(databaseName);
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(databaseName);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -300,9 +256,9 @@
                 @NonNull String databaseName,
                 @UserIdInt int userId,
                 @NonNull IAppSearchResultCallback callback) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(databaseName);
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(databaseName);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -328,10 +284,10 @@
                 @UserIdInt int userId,
                 @ElapsedRealtimeLong long binderCallStartTimeMillis,
                 @NonNull IAppSearchBatchResultCallback callback) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(databaseName);
-            Preconditions.checkNotNull(documentBundles);
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(databaseName);
+            Objects.requireNonNull(documentBundles);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -364,6 +320,8 @@
                             ++operationFailureCount;
                         }
                     }
+                    // Now that the batch has been written. Persist the newly written data.
+                    impl.persistToDisk(PersistType.Code.LITE);
                     invokeCallbackOnResult(callback, resultBuilder.build());
                 } catch (Throwable t) {
                     invokeCallbackOnError(callback, t);
@@ -400,11 +358,11 @@
                 @NonNull Map<String, List<String>> typePropertyPaths,
                 @UserIdInt int userId,
                 @NonNull IAppSearchBatchResultCallback callback) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(databaseName);
-            Preconditions.checkNotNull(namespace);
-            Preconditions.checkNotNull(uris);
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(databaseName);
+            Objects.requireNonNull(namespace);
+            Objects.requireNonNull(uris);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -445,11 +403,11 @@
                 @NonNull Bundle searchSpecBundle,
                 @UserIdInt int userId,
                 @NonNull IAppSearchResultCallback callback) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(databaseName);
-            Preconditions.checkNotNull(queryExpression);
-            Preconditions.checkNotNull(searchSpecBundle);
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(databaseName);
+            Objects.requireNonNull(queryExpression);
+            Objects.requireNonNull(searchSpecBundle);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -480,10 +438,10 @@
                 @NonNull Bundle searchSpecBundle,
                 @UserIdInt int userId,
                 @NonNull IAppSearchResultCallback callback) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(queryExpression);
-            Preconditions.checkNotNull(searchSpecBundle);
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(queryExpression);
+            Objects.requireNonNull(searchSpecBundle);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -512,7 +470,7 @@
                 long nextPageToken,
                 @UserIdInt int userId,
                 @NonNull IAppSearchResultCallback callback) {
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             // TODO(b/162450968) check nextPageToken is being advanced by the same uid as originally
@@ -631,7 +589,7 @@
                             }
                         }
                     }
-                    impl.persistToDisk();
+                    impl.persistToDisk(PersistType.Code.FULL);
                     invokeCallbackOnResult(callback,
                             AppSearchResult.newSuccessfulResult(migrationFailureBundles));
                 } catch (Throwable t) {
@@ -685,10 +643,10 @@
                 @NonNull List<String> uris,
                 @UserIdInt int userId,
                 @NonNull IAppSearchBatchResultCallback callback) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(databaseName);
-            Preconditions.checkNotNull(uris);
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(databaseName);
+            Objects.requireNonNull(uris);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -708,6 +666,8 @@
                             resultBuilder.setResult(uri, throwableToFailedResult(t));
                         }
                     }
+                    // Now that the batch has been written. Persist the newly written data.
+                    impl.persistToDisk(PersistType.Code.LITE);
                     invokeCallbackOnResult(callback, resultBuilder.build());
                 } catch (Throwable t) {
                     invokeCallbackOnError(callback, t);
@@ -723,11 +683,11 @@
                 @NonNull Bundle searchSpecBundle,
                 @UserIdInt int userId,
                 @NonNull IAppSearchResultCallback callback) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(databaseName);
-            Preconditions.checkNotNull(queryExpression);
-            Preconditions.checkNotNull(searchSpecBundle);
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(databaseName);
+            Objects.requireNonNull(queryExpression);
+            Objects.requireNonNull(searchSpecBundle);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -741,6 +701,8 @@
                             databaseName,
                             queryExpression,
                             new SearchSpec(searchSpecBundle));
+                    // Now that the batch has been written. Persist the newly written data.
+                    impl.persistToDisk(PersistType.Code.LITE);
                     invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null));
                 } catch (Throwable t) {
                     invokeCallbackOnError(callback, t);
@@ -754,9 +716,9 @@
                 @NonNull String databaseName,
                 @UserIdInt int userId,
                 @NonNull IAppSearchResultCallback callback) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(databaseName);
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(databaseName);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -785,7 +747,7 @@
                     verifyUserUnlocked(callingUserId);
                     AppSearchImpl impl =
                             mImplInstanceManager.getAppSearchImpl(callingUserId);
-                    impl.persistToDisk();
+                    impl.persistToDisk(PersistType.Code.FULL);
                 } catch (Throwable t) {
                     Log.e(TAG, "Unable to persist the data to disk", t);
                 }
@@ -794,7 +756,7 @@
 
         @Override
         public void initialize(@UserIdInt int userId, @NonNull IAppSearchResultCallback callback) {
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -825,7 +787,7 @@
         }
 
         private void verifyCallingPackage(int callingUid, @NonNull String callingPackage) {
-            Preconditions.checkNotNull(callingPackage);
+            Objects.requireNonNull(callingPackage);
             if (mPackageManagerInternal.getPackageUid(
                             callingPackage, /*flags=*/ 0, UserHandle.getUserId(callingUid))
                     != callingUid) {
@@ -873,13 +835,12 @@
         /**
          * Invokes the {@link IAppSearchBatchResultCallback} with an unexpected internal throwable.
          *
-         * <p>The throwable is converted to {@link ParcelableException}.
+         * <p>The throwable is converted to {@link AppSearchResult}.
          */
         private void invokeCallbackOnError(
-                IAppSearchBatchResultCallback callback, Throwable throwable) {
+                @NonNull IAppSearchBatchResultCallback callback, @NonNull Throwable throwable) {
             try {
-                //TODO(b/175067650) verify ParcelableException could propagate throwable correctly.
-                callback.onSystemError(new ParcelableException(throwable));
+                callback.onSystemError(throwableToFailedResult(throwable));
             } catch (RemoteException e) {
                 Log.e(TAG, "Unable to send error to the callback", e);
             }
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
index 1ed26d6..4de52fb 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
@@ -35,13 +35,14 @@
 import android.util.ArraySet;
 import android.util.Log;
 
-import com.android.internal.util.Preconditions;
+import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -153,7 +154,7 @@
      * AppSearchImpl.
      */
     static final String VISIBILITY_STORE_PREFIX =
-            AppSearchImpl.createPrefix(PACKAGE_NAME, DATABASE_NAME);
+            PrefixUtil.createPrefix(PACKAGE_NAME, DATABASE_NAME);
 
     /** Namespace of documents that contain visibility settings */
     private static final String NAMESPACE = "";
@@ -332,9 +333,9 @@
             @NonNull Set<String> schemasNotPlatformSurfaceable,
             @NonNull Map<String, List<PackageIdentifier>> schemasPackageAccessible)
             throws AppSearchException {
-        Preconditions.checkNotNull(prefix);
-        Preconditions.checkNotNull(schemasNotPlatformSurfaceable);
-        Preconditions.checkNotNull(schemasPackageAccessible);
+        Objects.requireNonNull(prefix);
+        Objects.requireNonNull(schemasNotPlatformSurfaceable);
+        Objects.requireNonNull(schemasPackageAccessible);
 
         // Persist the document
         GenericDocument.Builder<?> visibilityDocument =
@@ -382,8 +383,8 @@
     /** Checks whether {@code prefixedSchema} can be searched over by the {@code callerUid}. */
     public boolean isSchemaSearchableByCaller(
             @NonNull String prefix, @NonNull String prefixedSchema, int callerUid) {
-        Preconditions.checkNotNull(prefix);
-        Preconditions.checkNotNull(prefixedSchema);
+        Objects.requireNonNull(prefix);
+        Objects.requireNonNull(prefixedSchema);
 
         // We compare appIds here rather than direct uids because the package's uid may change based
         // on the user that's running.
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
index 5f8cbee..50ac054 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
@@ -16,10 +16,18 @@
 
 package com.android.server.appsearch.external.localstorage;
 
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.addPrefixToDocument;
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.createPackagePrefix;
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.createPrefix;
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.getDatabaseName;
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.getPackageName;
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.getPrefix;
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.removePrefix;
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.removePrefixesFromDocument;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.WorkerThread;
-import android.app.appsearch.AppSearchResult;
 import android.app.appsearch.AppSearchSchema;
 import android.app.appsearch.GenericDocument;
 import android.app.appsearch.GetByUriRequest;
@@ -39,7 +47,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
 import com.android.server.appsearch.external.localstorage.converter.GenericDocumentToProtoConverter;
 import com.android.server.appsearch.external.localstorage.converter.ResultCodeToProtoConverter;
 import com.android.server.appsearch.external.localstorage.converter.SchemaToProtoConverter;
@@ -66,7 +73,6 @@
 import com.google.android.icing.proto.PersistToDiskResultProto;
 import com.google.android.icing.proto.PersistType;
 import com.google.android.icing.proto.PropertyConfigProto;
-import com.google.android.icing.proto.PropertyProto;
 import com.google.android.icing.proto.PutResultProto;
 import com.google.android.icing.proto.ReportUsageResultProto;
 import com.google.android.icing.proto.ResetResultProto;
@@ -89,6 +95,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -132,10 +139,6 @@
 public final class AppSearchImpl implements Closeable {
     private static final String TAG = "AppSearchImpl";
 
-    @VisibleForTesting static final char DATABASE_DELIMITER = '/';
-
-    @VisibleForTesting static final char PACKAGE_DELIMITER = '$';
-
     @VisibleForTesting static final int OPTIMIZE_THRESHOLD_DOC_COUNT = 1000;
     @VisibleForTesting static final int OPTIMIZE_THRESHOLD_BYTES = 1_000_000; // 1MB
     @VisibleForTesting static final int CHECK_OPTIMIZE_INTERVAL = 100;
@@ -148,11 +151,12 @@
     @GuardedBy("mReadWriteLock")
     private final VisibilityStore mVisibilityStoreLocked;
 
-    // This map contains schemaTypes for all package-database prefixes. All values in the map are
-    // prefixed with the package-database prefix.
-    // TODO(b/172360376): Check if this can be replaced with an ArrayMap
+    // This map contains schema types and SchemaTypeConfigProtos for all package-database
+    // prefixes. It maps each package-database prefix to an inner-map. The inner-map maps each
+    // prefixed schema type to its respective SchemaTypeConfigProto.
     @GuardedBy("mReadWriteLock")
-    private final Map<String, Set<String>> mSchemaMapLocked = new HashMap<>();
+    private final Map<String, Map<String, SchemaTypeConfigProto>> mSchemaMapLocked =
+            new ArrayMap<>();
 
     // This map contains namespaces for all package-database prefixes. All values in the map are
     // prefixed with the package-database prefix.
@@ -182,9 +186,9 @@
             int userId,
             @NonNull String globalQuerierPackage)
             throws AppSearchException {
-        Preconditions.checkNotNull(icingDir);
-        Preconditions.checkNotNull(context);
-        Preconditions.checkNotNull(globalQuerierPackage);
+        Objects.requireNonNull(icingDir);
+        Objects.requireNonNull(context);
+        Objects.requireNonNull(globalQuerierPackage);
         AppSearchImpl appSearchImpl =
                 new AppSearchImpl(icingDir, context, userId, globalQuerierPackage);
         appSearchImpl.initializeVisibilityStore();
@@ -229,7 +233,7 @@
             // Populate schema map
             for (SchemaTypeConfigProto schema : schemaProto.getTypesList()) {
                 String prefixedSchemaType = schema.getSchemaType();
-                addToMap(mSchemaMapLocked, getPrefix(prefixedSchemaType), prefixedSchemaType);
+                addToMap(mSchemaMapLocked, getPrefix(prefixedSchemaType), schema);
             }
 
             // Populate namespace map
@@ -278,7 +282,7 @@
                 return;
             }
 
-            persistToDisk();
+            persistToDisk(PersistType.Code.FULL);
             mIcingSearchEngineLocked.close();
             mClosedLocked = true;
         } catch (AppSearchException e) {
@@ -364,7 +368,14 @@
             }
 
             // Update derived data structures.
-            mSchemaMapLocked.put(prefix, rewrittenSchemaResults.mRewrittenPrefixedTypes);
+            for (SchemaTypeConfigProto schemaTypeConfigProto :
+                    rewrittenSchemaResults.mRewrittenPrefixedTypes.values()) {
+                addToMap(mSchemaMapLocked, prefix, schemaTypeConfigProto);
+            }
+
+            for (String schemaType : rewrittenSchemaResults.mDeletedPrefixedTypes) {
+                removeFromMap(mSchemaMapLocked, prefix, schemaType);
+            }
 
             Set<String> prefixedSchemasNotPlatformSurfaceable =
                     new ArraySet<>(schemasNotPlatformSurfaceable.size());
@@ -586,7 +597,7 @@
         mReadWriteLock.readLock().lock();
         try {
             throwIfClosedLocked();
-
+            String prefix = createPrefix(packageName, databaseName);
             List<TypePropertyMask> nonPrefixedPropertyMasks =
                     TypePropertyPathToProtoConverter.toTypePropertyMaskList(typePropertyPaths);
             List<TypePropertyMask> prefixedPropertyMasks =
@@ -597,7 +608,7 @@
                 String prefixedType =
                         nonPrefixedType.equals(GetByUriRequest.PROJECTION_SCHEMA_TYPE_WILDCARD)
                                 ? nonPrefixedType
-                                : createPrefix(packageName, databaseName) + nonPrefixedType;
+                                : prefix + nonPrefixedType;
                 prefixedPropertyMasks.add(
                         typePropertyMask.toBuilder().setSchemaType(prefixedType).build());
             }
@@ -607,15 +618,17 @@
                             .build();
 
             GetResultProto getResultProto =
-                    mIcingSearchEngineLocked.get(
-                            createPrefix(packageName, databaseName) + namespace,
-                            uri,
-                            getResultSpec);
+                    mIcingSearchEngineLocked.get(prefix + namespace, uri, getResultSpec);
             checkSuccess(getResultProto.getStatus());
 
+            // The schema type map cannot be null at this point. It could only be null if no
+            // schema had ever been set for that prefix. Given we have retrieved a document from
+            // the index, we know a schema had to have been set.
+            Map<String, SchemaTypeConfigProto> schemaTypeMap = mSchemaMapLocked.get(prefix);
             DocumentProto.Builder documentBuilder = getResultProto.getDocument().toBuilder();
             removePrefixesFromDocument(documentBuilder);
-            return GenericDocumentToProtoConverter.toGenericDocument(documentBuilder.build());
+            return GenericDocumentToProtoConverter.toGenericDocument(
+                    documentBuilder.build(), prefix, schemaTypeMap);
         } finally {
             mReadWriteLock.readLock().unlock();
         }
@@ -726,7 +739,7 @@
                     }
                 } else {
                     // Client didn't specify certain schemas to search over, check all schemas
-                    Set<String> prefixedSchemas = mSchemaMapLocked.get(prefix);
+                    Set<String> prefixedSchemas = mSchemaMapLocked.get(prefix).keySet();
                     if (prefixedSchemas != null) {
                         for (String prefixedSchema : prefixedSchemas) {
                             if (packageName.equals(callerPackageName)
@@ -816,7 +829,7 @@
                         searchSpecBuilder.build(), scoringSpec, resultSpecBuilder.build());
         checkSuccess(searchResultProto.getStatus());
 
-        return rewriteSearchResultProto(searchResultProto);
+        return rewriteSearchResultProto(searchResultProto, mSchemaMapLocked);
     }
 
     /**
@@ -838,7 +851,7 @@
             SearchResultProto searchResultProto =
                     mIcingSearchEngineLocked.getNextPage(nextPageToken);
             checkSuccess(searchResultProto.getStatus());
-            return rewriteSearchResultProto(searchResultProto);
+            return rewriteSearchResultProto(searchResultProto, mSchemaMapLocked);
         } finally {
             mReadWriteLock.readLock().unlock();
         }
@@ -1104,22 +1117,31 @@
     /**
      * Persists all update/delete requests to the disk.
      *
-     * <p>If the app crashes after a call to PersistToDisk(), Icing would be able to fully recover
-     * all data written up to this point without a costly recovery process.
+     * <p>If the app crashes after a call to PersistToDisk with {@link PersistType.Code#FULL}, Icing
+     * would be able to fully recover all data written up to this point without a costly recovery
+     * process.
      *
-     * <p>If the app crashes before a call to PersistToDisk(), Icing would trigger a costly recovery
-     * process in next initialization. After that, Icing would still be able to recover all written
-     * data.
+     * <p>If the app crashes after a call to PersistToDisk with {@link PersistType.Code#LITE}, Icing
+     * would trigger a costly recovery process in next initialization. After that, Icing would still
+     * be able to recover all written data - excepting Usage data. Usage data is only guaranteed to
+     * be safe after a call to PersistToDisk with {@link PersistType.Code#FULL}
      *
+     * <p>If the app crashes after an update/delete request has been made, but before any call to
+     * PersistToDisk, then all data in Icing will be lost.
+     *
+     * @param persistType the amount of data to persist. {@link PersistType.Code#LITE} will only
+     *     persist the minimal amount of data to ensure all data can be recovered. {@link
+     *     PersistType.Code#FULL} will persist all data necessary to prevent data loss without
+     *     needing data recovery.
      * @throws AppSearchException on any error that AppSearch persist data to disk.
      */
-    public void persistToDisk() throws AppSearchException {
+    public void persistToDisk(@NonNull PersistType.Code persistType) throws AppSearchException {
         mReadWriteLock.writeLock().lock();
         try {
             throwIfClosedLocked();
 
             PersistToDiskResultProto persistToDiskResultProto =
-                    mIcingSearchEngineLocked.persistToDisk(PersistType.Code.FULL);
+                    mIcingSearchEngineLocked.persistToDisk(persistType);
             checkSuccess(persistToDiskResultProto.getStatus());
         } finally {
             mReadWriteLock.writeLock().unlock();
@@ -1189,8 +1211,8 @@
         // Any prefixed types that used to exist in the schema, but are deleted in the new one.
         final Set<String> mDeletedPrefixedTypes = new ArraySet<>();
 
-        // Prefixed types that were part of the new schema.
-        final Set<String> mRewrittenPrefixedTypes = new ArraySet<>();
+        // Map of prefixed schema types to SchemaTypeConfigProtos that were part of the new schema.
+        final Map<String, SchemaTypeConfigProto> mRewrittenPrefixedTypes = new ArrayMap<>();
     }
 
     /**
@@ -1238,7 +1260,7 @@
 
         // newTypesToProto is modified below, so we need a copy first
         RewrittenSchemaResults rewrittenSchemaResults = new RewrittenSchemaResults();
-        rewrittenSchemaResults.mRewrittenPrefixedTypes.addAll(newTypesToProto.keySet());
+        rewrittenSchemaResults.mRewrittenPrefixedTypes.putAll(newTypesToProto);
 
         // Combine the existing schema (which may have types from other prefixes) with this
         // prefix's new schema. Modifies the existingSchemaBuilder.
@@ -1264,99 +1286,6 @@
     }
 
     /**
-     * Prepends {@code prefix} to all types and namespaces mentioned anywhere in {@code
-     * documentBuilder}.
-     *
-     * @param documentBuilder The document to mutate
-     * @param prefix The prefix to add
-     */
-    @VisibleForTesting
-    static void addPrefixToDocument(
-            @NonNull DocumentProto.Builder documentBuilder, @NonNull String prefix) {
-        // Rewrite the type name to include/remove the prefix.
-        String newSchema = prefix + documentBuilder.getSchema();
-        documentBuilder.setSchema(newSchema);
-
-        // Rewrite the namespace to include/remove the prefix.
-        documentBuilder.setNamespace(prefix + documentBuilder.getNamespace());
-
-        // Recurse into derived documents
-        for (int propertyIdx = 0;
-                propertyIdx < documentBuilder.getPropertiesCount();
-                propertyIdx++) {
-            int documentCount = documentBuilder.getProperties(propertyIdx).getDocumentValuesCount();
-            if (documentCount > 0) {
-                PropertyProto.Builder propertyBuilder =
-                        documentBuilder.getProperties(propertyIdx).toBuilder();
-                for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
-                    DocumentProto.Builder derivedDocumentBuilder =
-                            propertyBuilder.getDocumentValues(documentIdx).toBuilder();
-                    addPrefixToDocument(derivedDocumentBuilder, prefix);
-                    propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
-                }
-                documentBuilder.setProperties(propertyIdx, propertyBuilder);
-            }
-        }
-    }
-
-    /**
-     * Removes any prefixes from types and namespaces mentioned anywhere in {@code documentBuilder}.
-     *
-     * @param documentBuilder The document to mutate
-     * @return Prefix name that was removed from the document.
-     * @throws AppSearchException if there are unexpected database prefixing errors.
-     */
-    @NonNull
-    @VisibleForTesting
-    static String removePrefixesFromDocument(@NonNull DocumentProto.Builder documentBuilder)
-            throws AppSearchException {
-        // Rewrite the type name and namespace to remove the prefix.
-        String schemaPrefix = getPrefix(documentBuilder.getSchema());
-        String namespacePrefix = getPrefix(documentBuilder.getNamespace());
-
-        if (!schemaPrefix.equals(namespacePrefix)) {
-            throw new AppSearchException(
-                    AppSearchResult.RESULT_INTERNAL_ERROR,
-                    "Found unexpected"
-                            + " multiple prefix names in document: "
-                            + schemaPrefix
-                            + ", "
-                            + namespacePrefix);
-        }
-
-        documentBuilder.setSchema(removePrefix(documentBuilder.getSchema()));
-        documentBuilder.setNamespace(removePrefix(documentBuilder.getNamespace()));
-
-        // Recurse into derived documents
-        for (int propertyIdx = 0;
-                propertyIdx < documentBuilder.getPropertiesCount();
-                propertyIdx++) {
-            int documentCount = documentBuilder.getProperties(propertyIdx).getDocumentValuesCount();
-            if (documentCount > 0) {
-                PropertyProto.Builder propertyBuilder =
-                        documentBuilder.getProperties(propertyIdx).toBuilder();
-                for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
-                    DocumentProto.Builder derivedDocumentBuilder =
-                            propertyBuilder.getDocumentValues(documentIdx).toBuilder();
-                    String nestedPrefix = removePrefixesFromDocument(derivedDocumentBuilder);
-                    if (!nestedPrefix.equals(schemaPrefix)) {
-                        throw new AppSearchException(
-                                AppSearchResult.RESULT_INTERNAL_ERROR,
-                                "Found unexpected multiple prefix names in document: "
-                                        + schemaPrefix
-                                        + ", "
-                                        + nestedPrefix);
-                    }
-                    propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
-                }
-                documentBuilder.setProperties(propertyIdx, propertyBuilder);
-            }
-        }
-
-        return schemaPrefix;
-    }
-
-    /**
      * Rewrites the search spec filters with {@code prefixes}.
      *
      * <p>This method should be only called in query methods and get the READ lock to keep thread
@@ -1443,9 +1372,9 @@
 
         if (allowedPrefixedSchemas.isEmpty()) {
             // If the client didn't specify any schema filters, search over all of their schemas
-            Set<String> prefixedSchemas = mSchemaMapLocked.get(prefix);
-            if (prefixedSchemas != null) {
-                allowedPrefixedSchemas.addAll(prefixedSchemas);
+            Map<String, SchemaTypeConfigProto> prefixedSchemaMap = mSchemaMapLocked.get(prefix);
+            if (prefixedSchemaMap != null) {
+                allowedPrefixedSchemas.addAll(prefixedSchemaMap.keySet());
             }
         }
         return allowedPrefixedSchemas;
@@ -1656,86 +1585,6 @@
         return mSchemaMapLocked.keySet();
     }
 
-    @NonNull
-    static String createPrefix(@NonNull String packageName, @NonNull String databaseName) {
-        return createPackagePrefix(packageName) + databaseName + DATABASE_DELIMITER;
-    }
-
-    @NonNull
-    private static String createPackagePrefix(@NonNull String packageName) {
-        return packageName + PACKAGE_DELIMITER;
-    }
-
-    /**
-     * Returns the package name that's contained within the {@code prefix}.
-     *
-     * @param prefix Prefix string that contains the package name inside of it. The package name
-     *     must be in the front of the string, and separated from the rest of the string by the
-     *     {@link #PACKAGE_DELIMITER}.
-     * @return Valid package name.
-     */
-    @NonNull
-    private static String getPackageName(@NonNull String prefix) {
-        int delimiterIndex = prefix.indexOf(PACKAGE_DELIMITER);
-        if (delimiterIndex == -1) {
-            // This should never happen if we construct our prefixes properly
-            Log.wtf(TAG, "Malformed prefix doesn't contain package delimiter: " + prefix);
-            return "";
-        }
-        return prefix.substring(0, delimiterIndex);
-    }
-
-    /**
-     * Returns the database name that's contained within the {@code prefix}.
-     *
-     * @param prefix Prefix string that contains the database name inside of it. The database name
-     *     must be between the {@link #PACKAGE_DELIMITER} and {@link #DATABASE_DELIMITER}
-     * @return Valid database name.
-     */
-    @NonNull
-    private static String getDatabaseName(@NonNull String prefix) {
-        int packageDelimiterIndex = prefix.indexOf(PACKAGE_DELIMITER);
-        int databaseDelimiterIndex = prefix.indexOf(DATABASE_DELIMITER);
-        if (packageDelimiterIndex == -1) {
-            // This should never happen if we construct our prefixes properly
-            Log.wtf(TAG, "Malformed prefix doesn't contain package delimiter: " + prefix);
-            return "";
-        }
-        if (databaseDelimiterIndex == -1) {
-            // This should never happen if we construct our prefixes properly
-            Log.wtf(TAG, "Malformed prefix doesn't contain database delimiter: " + prefix);
-            return "";
-        }
-        return prefix.substring(packageDelimiterIndex + 1, databaseDelimiterIndex);
-    }
-
-    @NonNull
-    private static String removePrefix(@NonNull String prefixedString) throws AppSearchException {
-        // The prefix is made up of the package, then the database. So we only need to find the
-        // database cutoff.
-        int delimiterIndex;
-        if ((delimiterIndex = prefixedString.indexOf(DATABASE_DELIMITER)) != -1) {
-            // Add 1 to include the char size of the DATABASE_DELIMITER
-            return prefixedString.substring(delimiterIndex + 1);
-        }
-        throw new AppSearchException(
-                AppSearchResult.RESULT_UNKNOWN_ERROR,
-                "The prefixed value doesn't contains a valid database name.");
-    }
-
-    @NonNull
-    private static String getPrefix(@NonNull String prefixedString) throws AppSearchException {
-        int databaseDelimiterIndex = prefixedString.indexOf(DATABASE_DELIMITER);
-        if (databaseDelimiterIndex == -1) {
-            throw new AppSearchException(
-                    AppSearchResult.RESULT_UNKNOWN_ERROR,
-                    "The databaseName prefixed value doesn't contain a valid database name.");
-        }
-
-        // Add 1 to include the char size of the DATABASE_DELIMITER
-        return prefixedString.substring(0, databaseDelimiterIndex + 1);
-    }
-
     private static void addToMap(
             Map<String, Set<String>> map, String prefix, String prefixedValue) {
         Set<String> values = map.get(prefix);
@@ -1746,6 +1595,26 @@
         values.add(prefixedValue);
     }
 
+    private static void addToMap(
+            Map<String, Map<String, SchemaTypeConfigProto>> map,
+            String prefix,
+            SchemaTypeConfigProto schemaTypeConfigProto) {
+        Map<String, SchemaTypeConfigProto> schemaTypeMap = map.get(prefix);
+        if (schemaTypeMap == null) {
+            schemaTypeMap = new ArrayMap<>();
+            map.put(prefix, schemaTypeMap);
+        }
+        schemaTypeMap.put(schemaTypeConfigProto.getSchemaType(), schemaTypeConfigProto);
+    }
+
+    private static void removeFromMap(
+            Map<String, Map<String, SchemaTypeConfigProto>> map, String prefix, String schemaType) {
+        Map<String, SchemaTypeConfigProto> schemaTypeMap = map.get(prefix);
+        if (schemaTypeMap != null) {
+            schemaTypeMap.remove(schemaType);
+        }
+    }
+
     /**
      * Checks the given status code and throws an {@link AppSearchException} if code is an error.
      *
@@ -1853,7 +1722,9 @@
     /** Remove the rewritten schema types from any result documents. */
     @NonNull
     @VisibleForTesting
-    static SearchResultPage rewriteSearchResultProto(@NonNull SearchResultProto searchResultProto)
+    static SearchResultPage rewriteSearchResultProto(
+            @NonNull SearchResultProto searchResultProto,
+            @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap)
             throws AppSearchException {
         // Parallel array of package names for each document search result.
         List<String> packageNames = new ArrayList<>(searchResultProto.getResultsCount());
@@ -1873,7 +1744,7 @@
             resultsBuilder.setResults(i, resultBuilder);
         }
         return SearchResultToProtoConverter.toSearchResultPage(
-                resultsBuilder, packageNames, databaseNames);
+                resultsBuilder, packageNames, databaseNames, schemaMap);
     }
 
     @GuardedBy("mReadWriteLock")
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLoggerHelper.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLoggerHelper.java
index 5680670..cdd7952 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLoggerHelper.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLoggerHelper.java
@@ -18,11 +18,12 @@
 
 import android.annotation.NonNull;
 
-import com.android.internal.util.Preconditions;
 import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats;
 
 import com.google.android.icing.proto.PutDocumentStatsProto;
 
+import java.util.Objects;
+
 /**
  * Class contains helper functions for logging.
  *
@@ -42,8 +43,8 @@
     static void copyNativeStats(
             @NonNull PutDocumentStatsProto fromNativeStats,
             @NonNull PutDocumentStats.Builder toStatsBuilder) {
-        Preconditions.checkNotNull(fromNativeStats);
-        Preconditions.checkNotNull(toStatsBuilder);
+        Objects.requireNonNull(fromNativeStats);
+        Objects.requireNonNull(toStatsBuilder);
         toStatsBuilder
                 .setNativeLatencyMillis(fromNativeStats.getLatencyMs())
                 .setNativeDocumentStoreLatencyMillis(fromNativeStats.getDocumentStoreLatencyMs())
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java
index d6b9da8..5ff56ab 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java
@@ -17,16 +17,18 @@
 package com.android.server.appsearch.external.localstorage.converter;
 
 import android.annotation.NonNull;
+import android.app.appsearch.AppSearchSchema;
 import android.app.appsearch.GenericDocument;
 
-import com.android.internal.util.Preconditions;
-
 import com.google.android.icing.proto.DocumentProto;
 import com.google.android.icing.proto.PropertyProto;
+import com.google.android.icing.proto.SchemaTypeConfigProto;
 import com.google.protobuf.ByteString;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
 
 /**
  * Translates a {@link GenericDocument} into a {@link DocumentProto}.
@@ -34,13 +36,20 @@
  * @hide
  */
 public final class GenericDocumentToProtoConverter {
+    private static final String[] EMPTY_STRING_ARRAY = new String[0];
+    private static final long[] EMPTY_LONG_ARRAY = new long[0];
+    private static final double[] EMPTY_DOUBLE_ARRAY = new double[0];
+    private static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0];
+    private static final byte[][] EMPTY_BYTES_ARRAY = new byte[0][0];
+    private static final GenericDocument[] EMPTY_DOCUMENT_ARRAY = new GenericDocument[0];
+
     private GenericDocumentToProtoConverter() {}
 
     /** Converts a {@link GenericDocument} into a {@link DocumentProto}. */
     @NonNull
     @SuppressWarnings("unchecked")
     public static DocumentProto toDocumentProto(@NonNull GenericDocument document) {
-        Preconditions.checkNotNull(document);
+        Objects.requireNonNull(document);
         DocumentProto.Builder mProtoBuilder = DocumentProto.newBuilder();
         mProtoBuilder
                 .setUri(document.getUri())
@@ -97,16 +106,34 @@
         return mProtoBuilder.build();
     }
 
-    /** Converts a {@link DocumentProto} into a {@link GenericDocument}. */
+    /**
+     * Converts a {@link DocumentProto} into a {@link GenericDocument}.
+     *
+     * <p>In the case that the {@link DocumentProto} object proto has no values set, the converter
+     * searches for the matching property name in the {@link SchemaTypeConfigProto} object for the
+     * document, and infers the correct default value to set for the empty property based on the
+     * data type of the property defined by the schema type.
+     *
+     * @param proto the document to convert to a {@link GenericDocument} instance. The document
+     *     proto should have its package + database prefix stripped from its fields.
+     * @param prefix the package + database prefix used searching the {@code schemaTypeMap}.
+     * @param schemaTypeMap map of prefixed schema type to {@link SchemaTypeConfigProto}, used for
+     *     looking up the default empty value to set for a document property that has all empty
+     *     values.
+     */
     @NonNull
-    public static GenericDocument toGenericDocument(@NonNull DocumentProto proto) {
-        Preconditions.checkNotNull(proto);
+    public static GenericDocument toGenericDocument(
+            @NonNull DocumentProto proto,
+            @NonNull String prefix,
+            @NonNull Map<String, SchemaTypeConfigProto> schemaTypeMap) {
+        Objects.requireNonNull(proto);
         GenericDocument.Builder<?> documentBuilder =
                 new GenericDocument.Builder<>(
                                 proto.getNamespace(), proto.getUri(), proto.getSchema())
                         .setScore(proto.getScore())
                         .setTtlMillis(proto.getTtlMs())
                         .setCreationTimestampMillis(proto.getCreationTimestampMs());
+        String prefixedSchemaType = prefix + proto.getSchema();
 
         for (int i = 0; i < proto.getPropertiesCount(); i++) {
             PropertyProto property = proto.getProperties(i);
@@ -144,13 +171,51 @@
             } else if (property.getDocumentValuesCount() > 0) {
                 GenericDocument[] values = new GenericDocument[property.getDocumentValuesCount()];
                 for (int j = 0; j < values.length; j++) {
-                    values[j] = toGenericDocument(property.getDocumentValues(j));
+                    values[j] =
+                            toGenericDocument(property.getDocumentValues(j), prefix, schemaTypeMap);
                 }
                 documentBuilder.setPropertyDocument(name, values);
             } else {
-                throw new IllegalStateException("Unknown type of value: " + name);
+                // TODO(b/184966497): Optimize by caching PropertyConfigProto
+                setEmptyProperty(name, documentBuilder, schemaTypeMap.get(prefixedSchemaType));
             }
         }
         return documentBuilder.build();
     }
+
+    private static void setEmptyProperty(
+            @NonNull String propertyName,
+            @NonNull GenericDocument.Builder<?> documentBuilder,
+            @NonNull SchemaTypeConfigProto schema) {
+        @AppSearchSchema.PropertyConfig.DataType int dataType = 0;
+        for (int i = 0; i < schema.getPropertiesCount(); ++i) {
+            if (propertyName.equals(schema.getProperties(i).getPropertyName())) {
+                dataType = schema.getProperties(i).getDataType().getNumber();
+                break;
+            }
+        }
+
+        switch (dataType) {
+            case AppSearchSchema.PropertyConfig.DATA_TYPE_STRING:
+                documentBuilder.setPropertyString(propertyName, EMPTY_STRING_ARRAY);
+                break;
+            case AppSearchSchema.PropertyConfig.DATA_TYPE_INT64:
+                documentBuilder.setPropertyLong(propertyName, EMPTY_LONG_ARRAY);
+                break;
+            case AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE:
+                documentBuilder.setPropertyDouble(propertyName, EMPTY_DOUBLE_ARRAY);
+                break;
+            case AppSearchSchema.PropertyConfig.DATA_TYPE_BOOLEAN:
+                documentBuilder.setPropertyBoolean(propertyName, EMPTY_BOOLEAN_ARRAY);
+                break;
+            case AppSearchSchema.PropertyConfig.DATA_TYPE_BYTES:
+                documentBuilder.setPropertyBytes(propertyName, EMPTY_BYTES_ARRAY);
+                break;
+            case AppSearchSchema.PropertyConfig.DATA_TYPE_DOCUMENT:
+                documentBuilder.setPropertyDocument(propertyName, EMPTY_DOCUMENT_ARRAY);
+                break;
+            default:
+                throw new IllegalStateException("Unknown type of value: " + propertyName);
+        }
+    }
 }
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java
index 800b073..e3fa7e0 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java
@@ -20,8 +20,6 @@
 import android.app.appsearch.AppSearchSchema;
 import android.util.Log;
 
-import com.android.internal.util.Preconditions;
-
 import com.google.android.icing.proto.DocumentIndexingConfig;
 import com.google.android.icing.proto.PropertyConfigProto;
 import com.google.android.icing.proto.SchemaTypeConfigProto;
@@ -30,6 +28,7 @@
 import com.google.android.icing.proto.TermMatchType;
 
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Translates an {@link AppSearchSchema} into a {@link SchemaTypeConfigProto}.
@@ -48,7 +47,7 @@
     @NonNull
     public static SchemaTypeConfigProto toSchemaTypeConfigProto(
             @NonNull AppSearchSchema schema, int version) {
-        Preconditions.checkNotNull(schema);
+        Objects.requireNonNull(schema);
         SchemaTypeConfigProto.Builder protoBuilder =
                 SchemaTypeConfigProto.newBuilder()
                         .setSchemaType(schema.getSchemaType())
@@ -64,7 +63,7 @@
     @NonNull
     private static PropertyConfigProto toPropertyConfigProto(
             @NonNull AppSearchSchema.PropertyConfig property) {
-        Preconditions.checkNotNull(property);
+        Objects.requireNonNull(property);
         PropertyConfigProto.Builder builder =
                 PropertyConfigProto.newBuilder().setPropertyName(property.getName());
 
@@ -116,7 +115,7 @@
      */
     @NonNull
     public static AppSearchSchema toAppSearchSchema(@NonNull SchemaTypeConfigProtoOrBuilder proto) {
-        Preconditions.checkNotNull(proto);
+        Objects.requireNonNull(proto);
         AppSearchSchema.Builder builder = new AppSearchSchema.Builder(proto.getSchemaType());
         List<PropertyConfigProto> properties = proto.getPropertiesList();
         for (int i = 0; i < properties.size(); i++) {
@@ -129,7 +128,7 @@
     @NonNull
     private static AppSearchSchema.PropertyConfig toPropertyConfig(
             @NonNull PropertyConfigProto proto) {
-        Preconditions.checkNotNull(proto);
+        Objects.requireNonNull(proto);
         switch (proto.getDataType()) {
             case STRING:
                 return toStringPropertyConfig(proto);
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java
index bf7e533..57c1590 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java
@@ -16,6 +16,8 @@
 
 package com.android.server.appsearch.external.localstorage.converter;
 
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.createPrefix;
+
 import android.annotation.NonNull;
 import android.app.appsearch.GenericDocument;
 import android.app.appsearch.SearchResult;
@@ -24,6 +26,7 @@
 
 import com.android.internal.util.Preconditions;
 
+import com.google.android.icing.proto.SchemaTypeConfigProto;
 import com.google.android.icing.proto.SearchResultProto;
 import com.google.android.icing.proto.SearchResultProtoOrBuilder;
 import com.google.android.icing.proto.SnippetMatchProto;
@@ -31,6 +34,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Translates a {@link SearchResultProto} into {@link SearchResult}s.
@@ -49,13 +53,17 @@
      * @param databaseNames A parallel array of database names. The database name at index 'i' of
      *     this list shold be the database that indexed the document at index 'i' of
      *     proto.getResults(i).
+     * @param schemaMap A map of prefixes to an inner-map of prefixed schema type to
+     *     SchemaTypeConfigProtos, used for setting a default value for results with DocumentProtos
+     *     that have empty values.
      * @return {@link SearchResultPage} of results.
      */
     @NonNull
     public static SearchResultPage toSearchResultPage(
             @NonNull SearchResultProtoOrBuilder proto,
             @NonNull List<String> packageNames,
-            @NonNull List<String> databaseNames) {
+            @NonNull List<String> databaseNames,
+            @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap) {
         Preconditions.checkArgument(
                 proto.getResultsCount() == packageNames.size(),
                 "Size of results does not match the number of package names.");
@@ -63,8 +71,14 @@
         bundle.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, proto.getNextPageToken());
         ArrayList<Bundle> resultBundles = new ArrayList<>(proto.getResultsCount());
         for (int i = 0; i < proto.getResultsCount(); i++) {
+            String prefix = createPrefix(packageNames.get(i), databaseNames.get(i));
+            Map<String, SchemaTypeConfigProto> schemaTypeMap = schemaMap.get(prefix);
             SearchResult result =
-                    toSearchResult(proto.getResults(i), packageNames.get(i), databaseNames.get(i));
+                    toSearchResult(
+                            proto.getResults(i),
+                            packageNames.get(i),
+                            databaseNames.get(i),
+                            schemaTypeMap);
             resultBundles.add(result.getBundle());
         }
         bundle.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, resultBundles);
@@ -77,15 +91,21 @@
      * @param proto The proto to be converted.
      * @param packageName The package name associated with the document in {@code proto}.
      * @param databaseName The database name associated with the document in {@code proto}.
+     * @param schemaTypeToProtoMap A map of prefixed schema types to their corresponding
+     *     SchemaTypeConfigProto, used for setting a default value for results with DocumentProtos
+     *     that have empty values.
      * @return A {@link SearchResult} bundle.
      */
     @NonNull
     private static SearchResult toSearchResult(
             @NonNull SearchResultProto.ResultProtoOrBuilder proto,
             @NonNull String packageName,
-            @NonNull String databaseName) {
+            @NonNull String databaseName,
+            @NonNull Map<String, SchemaTypeConfigProto> schemaTypeToProtoMap) {
+        String prefix = createPrefix(packageName, databaseName);
         GenericDocument document =
-                GenericDocumentToProtoConverter.toGenericDocument(proto.getDocument());
+                GenericDocumentToProtoConverter.toGenericDocument(
+                        proto.getDocument(), prefix, schemaTypeToProtoMap);
         SearchResult.Builder builder =
                 new SearchResult.Builder(packageName, databaseName)
                         .setGenericDocument(document)
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java
index d9e8adb..8f9e9bd 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java
@@ -19,13 +19,13 @@
 import android.annotation.NonNull;
 import android.app.appsearch.SearchSpec;
 
-import com.android.internal.util.Preconditions;
-
 import com.google.android.icing.proto.ResultSpecProto;
 import com.google.android.icing.proto.ScoringSpecProto;
 import com.google.android.icing.proto.SearchSpecProto;
 import com.google.android.icing.proto.TermMatchType;
 
+import java.util.Objects;
+
 /**
  * Translates a {@link SearchSpec} into icing search protos.
  *
@@ -37,7 +37,7 @@
     /** Extracts {@link SearchSpecProto} information from a {@link SearchSpec}. */
     @NonNull
     public static SearchSpecProto toSearchSpecProto(@NonNull SearchSpec spec) {
-        Preconditions.checkNotNull(spec);
+        Objects.requireNonNull(spec);
         SearchSpecProto.Builder protoBuilder =
                 SearchSpecProto.newBuilder()
                         .addAllSchemaTypeFilters(spec.getFilterSchemas())
@@ -56,7 +56,7 @@
     /** Extracts {@link ResultSpecProto} information from a {@link SearchSpec}. */
     @NonNull
     public static ResultSpecProto toResultSpecProto(@NonNull SearchSpec spec) {
-        Preconditions.checkNotNull(spec);
+        Objects.requireNonNull(spec);
         return ResultSpecProto.newBuilder()
                 .setNumPerPage(spec.getResultCountPerPage())
                 .setSnippetSpec(
@@ -73,7 +73,7 @@
     /** Extracts {@link ScoringSpecProto} information from a {@link SearchSpec}. */
     @NonNull
     public static ScoringSpecProto toScoringSpecProto(@NonNull SearchSpec spec) {
-        Preconditions.checkNotNull(spec);
+        Objects.requireNonNull(spec);
         ScoringSpecProto.Builder protoBuilder = ScoringSpecProto.newBuilder();
 
         @SearchSpec.Order int orderCode = spec.getOrder();
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResponseToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResponseToProtoConverter.java
index a0f39ec..ed73593 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResponseToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResponseToProtoConverter.java
@@ -19,10 +19,10 @@
 import android.annotation.NonNull;
 import android.app.appsearch.SetSchemaResponse;
 
-import com.android.internal.util.Preconditions;
-
 import com.google.android.icing.proto.SetSchemaResultProto;
 
+import java.util.Objects;
+
 /**
  * Translates a {@link SetSchemaResultProto} into {@link SetSchemaResponse}.
  *
@@ -42,8 +42,8 @@
     @NonNull
     public static SetSchemaResponse toSetSchemaResponse(
             @NonNull SetSchemaResultProto proto, @NonNull String prefix) {
-        Preconditions.checkNotNull(proto);
-        Preconditions.checkNotNull(prefix);
+        Objects.requireNonNull(proto);
+        Objects.requireNonNull(prefix);
         SetSchemaResponse.Builder builder = new SetSchemaResponse.Builder();
 
         for (int i = 0; i < proto.getDeletedSchemaTypesCount(); i++) {
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/TypePropertyPathToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/TypePropertyPathToProtoConverter.java
index 6f6dad2..acf04ef 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/TypePropertyPathToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/TypePropertyPathToProtoConverter.java
@@ -18,13 +18,12 @@
 
 import android.annotation.NonNull;
 
-import com.android.internal.util.Preconditions;
-
 import com.google.android.icing.proto.TypePropertyMask;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Translates a <code>Map<String, List<String>></code> into <code>List<TypePropertyMask></code>.
@@ -38,7 +37,7 @@
     @NonNull
     public static List<TypePropertyMask> toTypePropertyMaskList(
             @NonNull Map<String, List<String>> typePropertyPaths) {
-        Preconditions.checkNotNull(typePropertyPaths);
+        Objects.requireNonNull(typePropertyPaths);
         List<TypePropertyMask> typePropertyMasks = new ArrayList<>(typePropertyPaths.size());
         for (Map.Entry<String, List<String>> e : typePropertyPaths.entrySet()) {
             typePropertyMasks.add(
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java
index a724f95..cf640c1 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java
@@ -19,10 +19,9 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 
-import com.android.internal.util.Preconditions;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
 
 /**
  * A class for setting basic information to log for all function calls.
@@ -75,8 +74,8 @@
     private final int mNumOperationsFailed;
 
     CallStats(@NonNull Builder builder) {
-        Preconditions.checkNotNull(builder);
-        mGeneralStats = Preconditions.checkNotNull(builder.mGeneralStatsBuilder).build();
+        Objects.requireNonNull(builder);
+        mGeneralStats = Objects.requireNonNull(builder.mGeneralStatsBuilder).build();
         mCallType = builder.mCallType;
         mEstimatedBinderLatencyMillis = builder.mEstimatedBinderLatencyMillis;
         mNumOperationsSucceeded = builder.mNumOperationsSucceeded;
@@ -140,8 +139,8 @@
 
         /** Builder takes {@link GeneralStats.Builder}. */
         public Builder(@NonNull String packageName, @NonNull String database) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(database);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(database);
             mGeneralStatsBuilder = new GeneralStats.Builder(packageName, database);
         }
 
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/GeneralStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/GeneralStats.java
index 8ce8eda..53c1ee3 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/GeneralStats.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/GeneralStats.java
@@ -19,7 +19,7 @@
 import android.annotation.NonNull;
 import android.app.appsearch.AppSearchResult;
 
-import com.android.internal.util.Preconditions;
+import java.util.Objects;
 
 /**
  * A class for holding general logging information.
@@ -48,9 +48,9 @@
     private final int mTotalLatencyMillis;
 
     GeneralStats(@NonNull Builder builder) {
-        Preconditions.checkNotNull(builder);
-        mPackageName = Preconditions.checkNotNull(builder.mPackageName);
-        mDatabase = Preconditions.checkNotNull(builder.mDatabase);
+        Objects.requireNonNull(builder);
+        mPackageName = Objects.requireNonNull(builder.mPackageName);
+        mDatabase = Objects.requireNonNull(builder.mDatabase);
         mStatusCode = builder.mStatusCode;
         mTotalLatencyMillis = builder.mTotalLatencyMillis;
     }
@@ -92,8 +92,8 @@
          * @param database name of the database logging stats
          */
         public Builder(@NonNull String packageName, @NonNull String database) {
-            mPackageName = Preconditions.checkNotNull(packageName);
-            mDatabase = Preconditions.checkNotNull(database);
+            mPackageName = Objects.requireNonNull(packageName);
+            mDatabase = Objects.requireNonNull(database);
         }
 
         /** Sets status code returned from {@link AppSearchResult#getResultCode()} */
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java
index c1f6fb1..d031172 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 
-import com.android.internal.util.Preconditions;
+import java.util.Objects;
 
 /**
  * A class for holding detailed stats to log for each individual document put by a {@link
@@ -60,8 +60,8 @@
     private final boolean mNativeExceededMaxNumTokens;
 
     PutDocumentStats(@NonNull Builder builder) {
-        Preconditions.checkNotNull(builder);
-        mGeneralStats = Preconditions.checkNotNull(builder.mGeneralStatsBuilder).build();
+        Objects.requireNonNull(builder);
+        mGeneralStats = Objects.requireNonNull(builder.mGeneralStatsBuilder).build();
         mGenerateDocumentProtoLatencyMillis = builder.mGenerateDocumentProtoLatencyMillis;
         mRewriteDocumentTypesLatencyMillis = builder.mRewriteDocumentTypesLatencyMillis;
         mNativeLatencyMillis = builder.mNativeLatencyMillis;
@@ -142,8 +142,8 @@
 
         /** Builder takes {@link GeneralStats.Builder}. */
         public Builder(@NonNull String packageName, @NonNull String database) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(database);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(database);
             mGeneralStatsBuilder = new GeneralStats.Builder(packageName, database);
         }
 
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/util/PrefixUtil.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/util/PrefixUtil.java
new file mode 100644
index 0000000..9ae9f18
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/util/PrefixUtil.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appsearch.external.localstorage.util;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.exceptions.AppSearchException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.android.icing.proto.DocumentProto;
+import com.google.android.icing.proto.PropertyProto;
+
+/**
+ * Provides utility functions for working with package + database prefixes.
+ *
+ * @hide
+ */
+public class PrefixUtil {
+    private static final String TAG = "AppSearchPrefixUtil";
+
+    @VisibleForTesting public static final char DATABASE_DELIMITER = '/';
+
+    @VisibleForTesting public static final char PACKAGE_DELIMITER = '$';
+
+    private PrefixUtil() {}
+
+    /** Creates prefix string for given package name and database name. */
+    @NonNull
+    public static String createPrefix(@NonNull String packageName, @NonNull String databaseName) {
+        return packageName + PACKAGE_DELIMITER + databaseName + DATABASE_DELIMITER;
+    }
+    /** Creates prefix string for given package name. */
+    @NonNull
+    public static String createPackagePrefix(@NonNull String packageName) {
+        return packageName + PACKAGE_DELIMITER;
+    }
+
+    /**
+     * Returns the package name that's contained within the {@code prefix}.
+     *
+     * @param prefix Prefix string that contains the package name inside of it. The package name
+     *     must be in the front of the string, and separated from the rest of the string by the
+     *     {@link #PACKAGE_DELIMITER}.
+     * @return Valid package name.
+     */
+    @NonNull
+    public static String getPackageName(@NonNull String prefix) {
+        int delimiterIndex = prefix.indexOf(PACKAGE_DELIMITER);
+        if (delimiterIndex == -1) {
+            // This should never happen if we construct our prefixes properly
+            Log.wtf(TAG, "Malformed prefix doesn't contain package delimiter: " + prefix);
+            return "";
+        }
+        return prefix.substring(0, delimiterIndex);
+    }
+
+    /**
+     * Returns the database name that's contained within the {@code prefix}.
+     *
+     * @param prefix Prefix string that contains the database name inside of it. The database name
+     *     must be between the {@link #PACKAGE_DELIMITER} and {@link #DATABASE_DELIMITER}
+     * @return Valid database name.
+     */
+    @NonNull
+    public static String getDatabaseName(@NonNull String prefix) {
+        // TODO (b/184050178) Start database delimiter index search from after package delimiter
+        int packageDelimiterIndex = prefix.indexOf(PACKAGE_DELIMITER);
+        int databaseDelimiterIndex = prefix.indexOf(DATABASE_DELIMITER);
+        if (packageDelimiterIndex == -1) {
+            // This should never happen if we construct our prefixes properly
+            Log.wtf(TAG, "Malformed prefix doesn't contain package delimiter: " + prefix);
+            return "";
+        }
+        if (databaseDelimiterIndex == -1) {
+            // This should never happen if we construct our prefixes properly
+            Log.wtf(TAG, "Malformed prefix doesn't contain database delimiter: " + prefix);
+            return "";
+        }
+        return prefix.substring(packageDelimiterIndex + 1, databaseDelimiterIndex);
+    }
+
+    /**
+     * Creates a string with the package and database prefix removed from the input string.
+     *
+     * @param prefixedString a string containing a package and database prefix.
+     * @return a string with the package and database prefix removed.
+     * @throws AppSearchException if the prefixed value does not contain a valid database name.
+     */
+    @NonNull
+    public static String removePrefix(@NonNull String prefixedString) throws AppSearchException {
+        // The prefix is made up of the package, then the database. So we only need to find the
+        // database cutoff.
+        int delimiterIndex;
+        if ((delimiterIndex = prefixedString.indexOf(DATABASE_DELIMITER)) != -1) {
+            // Add 1 to include the char size of the DATABASE_DELIMITER
+            return prefixedString.substring(delimiterIndex + 1);
+        }
+        throw new AppSearchException(
+                AppSearchResult.RESULT_UNKNOWN_ERROR,
+                "The prefixed value doesn't contains a valid database name.");
+    }
+
+    /**
+     * Creates a package and database prefix string from the input string.
+     *
+     * @param prefixedString a string containing a package and database prefix.
+     * @return a string with the package and database prefix
+     * @throws AppSearchException if the prefixed value does not contain a valid database name.
+     */
+    @NonNull
+    public static String getPrefix(@NonNull String prefixedString) throws AppSearchException {
+        int databaseDelimiterIndex = prefixedString.indexOf(DATABASE_DELIMITER);
+        if (databaseDelimiterIndex == -1) {
+            throw new AppSearchException(
+                    AppSearchResult.RESULT_UNKNOWN_ERROR,
+                    "The databaseName prefixed value doesn't contain a valid database name.");
+        }
+
+        // Add 1 to include the char size of the DATABASE_DELIMITER
+        return prefixedString.substring(0, databaseDelimiterIndex + 1);
+    }
+
+    /**
+     * Prepends {@code prefix} to all types and namespaces mentioned anywhere in {@code
+     * documentBuilder}.
+     *
+     * @param documentBuilder The document to mutate
+     * @param prefix The prefix to add
+     */
+    public static void addPrefixToDocument(
+            @NonNull DocumentProto.Builder documentBuilder, @NonNull String prefix) {
+        // Rewrite the type name to include/remove the prefix.
+        String newSchema = prefix + documentBuilder.getSchema();
+        documentBuilder.setSchema(newSchema);
+
+        // Rewrite the namespace to include/remove the prefix.
+        documentBuilder.setNamespace(prefix + documentBuilder.getNamespace());
+
+        // Recurse into derived documents
+        for (int propertyIdx = 0;
+                propertyIdx < documentBuilder.getPropertiesCount();
+                propertyIdx++) {
+            int documentCount = documentBuilder.getProperties(propertyIdx).getDocumentValuesCount();
+            if (documentCount > 0) {
+                PropertyProto.Builder propertyBuilder =
+                        documentBuilder.getProperties(propertyIdx).toBuilder();
+                for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
+                    DocumentProto.Builder derivedDocumentBuilder =
+                            propertyBuilder.getDocumentValues(documentIdx).toBuilder();
+                    addPrefixToDocument(derivedDocumentBuilder, prefix);
+                    propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
+                }
+                documentBuilder.setProperties(propertyIdx, propertyBuilder);
+            }
+        }
+    }
+
+    /**
+     * Removes any prefixes from types and namespaces mentioned anywhere in {@code documentBuilder}.
+     *
+     * @param documentBuilder The document to mutate
+     * @return Prefix name that was removed from the document.
+     * @throws AppSearchException if there are unexpected database prefixing errors.
+     */
+    @NonNull
+    public static String removePrefixesFromDocument(@NonNull DocumentProto.Builder documentBuilder)
+            throws AppSearchException {
+        // Rewrite the type name and namespace to remove the prefix.
+        String schemaPrefix = getPrefix(documentBuilder.getSchema());
+        String namespacePrefix = getPrefix(documentBuilder.getNamespace());
+
+        if (!schemaPrefix.equals(namespacePrefix)) {
+            throw new AppSearchException(
+                    AppSearchResult.RESULT_INTERNAL_ERROR,
+                    "Found unexpected"
+                            + " multiple prefix names in document: "
+                            + schemaPrefix
+                            + ", "
+                            + namespacePrefix);
+        }
+
+        documentBuilder.setSchema(removePrefix(documentBuilder.getSchema()));
+        documentBuilder.setNamespace(removePrefix(documentBuilder.getNamespace()));
+
+        // Recurse into derived documents
+        for (int propertyIdx = 0;
+                propertyIdx < documentBuilder.getPropertiesCount();
+                propertyIdx++) {
+            int documentCount = documentBuilder.getProperties(propertyIdx).getDocumentValuesCount();
+            if (documentCount > 0) {
+                PropertyProto.Builder propertyBuilder =
+                        documentBuilder.getProperties(propertyIdx).toBuilder();
+                for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
+                    DocumentProto.Builder derivedDocumentBuilder =
+                            propertyBuilder.getDocumentValues(documentIdx).toBuilder();
+                    String nestedPrefix = removePrefixesFromDocument(derivedDocumentBuilder);
+                    if (!nestedPrefix.equals(schemaPrefix)) {
+                        throw new AppSearchException(
+                                AppSearchResult.RESULT_INTERNAL_ERROR,
+                                "Found unexpected multiple prefix names in document: "
+                                        + schemaPrefix
+                                        + ", "
+                                        + nestedPrefix);
+                    }
+                    propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
+                }
+                documentBuilder.setProperties(propertyIdx, propertyBuilder);
+            }
+        }
+
+        return schemaPrefix;
+    }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
index 1c04d99..731ab35 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
@@ -29,7 +29,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.Preconditions;
 import com.android.server.appsearch.external.localstorage.AppSearchLogger;
 import com.android.server.appsearch.external.localstorage.stats.CallStats;
 import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats;
@@ -38,6 +37,7 @@
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Random;
 
 /**
@@ -162,15 +162,15 @@
      * Westworld constructor
      */
     public PlatformLogger(@NonNull Context context, int userId, @NonNull Config config) {
-        mContext = Preconditions.checkNotNull(context);
-        mConfig = Preconditions.checkNotNull(config);
+        mContext = Objects.requireNonNull(context);
+        mConfig = Objects.requireNonNull(config);
         mUserId = userId;
     }
 
     /** Logs {@link CallStats}. */
     @Override
     public void logStats(@NonNull CallStats stats) {
-        Preconditions.checkNotNull(stats);
+        Objects.requireNonNull(stats);
         synchronized (mLock) {
             if (shouldLogForTypeLocked(stats.getCallType())) {
                 logStatsImplLocked(stats);
@@ -181,7 +181,7 @@
     /** Logs {@link PutDocumentStats}. */
     @Override
     public void logStats(@NonNull PutDocumentStats stats) {
-        Preconditions.checkNotNull(stats);
+        Objects.requireNonNull(stats);
         synchronized (mLock) {
             if (shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)) {
                 logStatsImplLocked(stats);
@@ -197,7 +197,7 @@
     */
     public int removeCachedUidForPackage(@NonNull String packageName) {
         // TODO(b/173532925) This needs to be called when we get PACKAGE_REMOVED intent
-        Preconditions.checkNotNull(packageName);
+        Objects.requireNonNull(packageName);
         synchronized (mLock) {
             Integer uid = mPackageUidCacheLocked.remove(packageName);
             return uid != null ? uid : Process.INVALID_UID;
diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt
index e46c147..f99664b 100644
--- a/apex/appsearch/synced_jetpack_changeid.txt
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -1 +1 @@
-I925ec12f4901c7759976c344ba3428210aada8ad
+If9d1d770d2327d7d0db7d82acfc54787b5de64bc
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java
index f0de496..6193367 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java
@@ -40,11 +40,11 @@
 
 import androidx.test.core.app.ApplicationProvider;
 
-import com.google.common.base.Preconditions;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.SettableFuture;
 
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -81,8 +81,8 @@
 
     private AppSearchSessionShimImpl(
             @NonNull AppSearchSession session, @NonNull ExecutorService executor) {
-        mAppSearchSession = Preconditions.checkNotNull(session);
-        mExecutor = Preconditions.checkNotNull(executor);
+        mAppSearchSession = Objects.requireNonNull(session);
+        mExecutor = Objects.requireNonNull(executor);
     }
 
     @Override
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
index 5042ce0..c35849d 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
@@ -30,11 +30,11 @@
 
 import androidx.test.core.app.ApplicationProvider;
 
-import com.google.common.base.Preconditions;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.SettableFuture;
 
+import java.util.Objects;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
@@ -69,8 +69,8 @@
 
     private GlobalSearchSessionShimImpl(
             @NonNull GlobalSearchSession session, @NonNull ExecutorService executor) {
-        mGlobalSearchSession = Preconditions.checkNotNull(session);
-        mExecutor = Preconditions.checkNotNull(executor);
+        mGlobalSearchSession = Objects.requireNonNull(session);
+        mExecutor = Objects.requireNonNull(executor);
     }
 
     @NonNull
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java
index 5f26e8c..72078f8 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java
@@ -22,12 +22,12 @@
 import android.app.appsearch.SearchResults;
 import android.app.appsearch.SearchResultsShim;
 
-import com.google.common.base.Preconditions;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.SettableFuture;
 
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -40,8 +40,8 @@
     private final SearchResults mSearchResults;
 
     SearchResultsShimImpl(@NonNull SearchResults searchResults, @NonNull Executor executor) {
-        mExecutor = Preconditions.checkNotNull(executor);
-        mSearchResults = Preconditions.checkNotNull(searchResults);
+        mExecutor = Objects.requireNonNull(executor);
+        mSearchResults = Objects.requireNonNull(searchResults);
     }
 
     @NonNull
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
index 2069043..494945d 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
@@ -187,9 +187,8 @@
      * <p>Removed documents will no longer be surfaced by {@link #search} or {@link #getByUri}
      * calls.
      *
-     * <p><b>NOTE:</b>By default, documents are removed via a soft delete operation. Once the
-     * document crosses the count threshold or byte usage threshold, the documents will be removed
-     * from disk.
+     * <p>Once the database crosses the document count or byte usage threshold, removed documents
+     * will be deleted from disk.
      *
      * @param request {@link RemoveByUriRequest} with URIs and namespace to remove from the index.
      * @return a {@link ListenableFuture} which resolves to an {@link AppSearchBatchResult}. The
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 31da201..03d9a96 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -1684,17 +1684,13 @@
             }
         }
 
-        if ((flags & AlarmManager.FLAG_IDLE_UNTIL) != 0) {
-            // Do not support windows for idle-until alarms.
-            windowLength = AlarmManager.WINDOW_EXACT;
-        }
-
         // Snap the window to reasonable limits.
         if (windowLength > INTERVAL_DAY) {
             Slog.w(TAG, "Window length " + windowLength
                     + "ms suspiciously long; limiting to 1 day");
             windowLength = INTERVAL_DAY;
-        } else if (windowLength > 0 && windowLength < mConstants.MIN_WINDOW) {
+        } else if (windowLength > 0 && windowLength < mConstants.MIN_WINDOW
+                && (flags & FLAG_PRIORITIZE) == 0) {
             if (CompatChanges.isChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS,
                     callingPackage, UserHandle.getUserHandleForUid(callingUid))) {
                 Slog.w(TAG, "Window length " + windowLength + "ms too short; expanding to "
@@ -1741,7 +1737,7 @@
         final long triggerElapsed = (nominalTrigger > minTrigger) ? nominalTrigger : minTrigger;
 
         final long maxElapsed;
-        if (windowLength == AlarmManager.WINDOW_EXACT) {
+        if (windowLength == 0) {
             maxElapsed = triggerElapsed;
         } else if (windowLength < 0) {
             maxElapsed = maxTriggerTime(nowElapsed, triggerElapsed, interval);
@@ -2145,17 +2141,63 @@
                         + " does not belong to the calling uid " + callingUid);
             }
 
-            final boolean allowWhileIdle = (flags & FLAG_ALLOW_WHILE_IDLE) != 0;
-            final boolean exact = (windowLength == AlarmManager.WINDOW_EXACT);
+            // Repeating alarms must use PendingIntent, not direct listener
+            if (interval != 0 && directReceiver != null) {
+                throw new IllegalArgumentException("Repeating alarms cannot use AlarmReceivers");
+            }
 
-            // make sure the caller is allowed to use the requested kind of alarm, and also
+            if (workSource != null) {
+                getContext().enforcePermission(
+                        android.Manifest.permission.UPDATE_DEVICE_STATS,
+                        Binder.getCallingPid(), callingUid, "AlarmManager.set");
+            }
+
+            if ((flags & AlarmManager.FLAG_IDLE_UNTIL) != 0) {
+                // Only the system can use FLAG_IDLE_UNTIL -- this is used to tell the alarm
+                // manager when to come out of idle mode, which is only for DeviceIdleController.
+                if (callingUid != Process.SYSTEM_UID) {
+                    // TODO (b/169463012): Throw instead of tolerating this mistake.
+                    flags &= ~AlarmManager.FLAG_IDLE_UNTIL;
+                } else {
+                    // Do not support windows for idle-until alarms.
+                    windowLength = 0;
+                }
+            }
+
+            // Remove flags reserved for the service, we will apply those later as appropriate.
+            flags &= ~(FLAG_WAKE_FROM_IDLE | FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED
+                    | FLAG_ALLOW_WHILE_IDLE_COMPAT);
+
+            // If this alarm is for an alarm clock, then it must be exact and we will
+            // use it to wake early from idle if needed.
+            if (alarmClock != null) {
+                flags |= FLAG_WAKE_FROM_IDLE;
+                windowLength = 0;
+
+            // If the caller is a core system component or on the user's allowlist, and not calling
+            // to do work on behalf of someone else, then always set ALLOW_WHILE_IDLE_UNRESTRICTED.
+            // This means we will allow these alarms to go off as normal even while idle, with no
+            // timing restrictions.
+            } else if (workSource == null && (UserHandle.isCore(callingUid)
+                    || UserHandle.isSameApp(callingUid, mSystemUiUid)
+                    || ((mAppStateTracker != null)
+                    && mAppStateTracker.isUidPowerSaveUserExempt(callingUid)))) {
+                flags |= FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
+                flags &= ~(FLAG_ALLOW_WHILE_IDLE | FLAG_PRIORITIZE);
+            }
+
+            final boolean allowWhileIdle = (flags & FLAG_ALLOW_WHILE_IDLE) != 0;
+            final boolean exact = (windowLength == 0);
+
+            // Make sure the caller is allowed to use the requested kind of alarm, and also
             // decide what quota and broadcast options to use.
             Bundle idleOptions = null;
             if ((flags & FLAG_PRIORITIZE) != 0) {
                 getContext().enforcePermission(
                         Manifest.permission.SCHEDULE_PRIORITIZED_ALARM,
                         Binder.getCallingPid(), callingUid, "AlarmManager.setPrioritized");
-                flags &= ~(FLAG_ALLOW_WHILE_IDLE | FLAG_ALLOW_WHILE_IDLE_COMPAT);
+                // The API doesn't allow using both together.
+                flags &= ~FLAG_ALLOW_WHILE_IDLE;
             } else if (exact || allowWhileIdle) {
                 final boolean needsPermission;
                 boolean lowerQuota;
@@ -2193,55 +2235,11 @@
                 }
             }
 
-            // Repeating alarms must use PendingIntent, not direct listener
-            if (interval != 0) {
-                if (directReceiver != null) {
-                    throw new IllegalArgumentException(
-                            "Repeating alarms cannot use AlarmReceivers");
-                }
-            }
-
-            if (workSource != null) {
-                getContext().enforcePermission(
-                        android.Manifest.permission.UPDATE_DEVICE_STATS,
-                        Binder.getCallingPid(), callingUid, "AlarmManager.set");
-            }
-
-            // No incoming callers can request either WAKE_FROM_IDLE or
-            // ALLOW_WHILE_IDLE_UNRESTRICTED -- we will apply those later as appropriate.
-            flags &= ~(FLAG_WAKE_FROM_IDLE | FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED);
-
-            // Only the system can use FLAG_IDLE_UNTIL -- this is used to tell the alarm
-            // manager when to come out of idle mode, which is only for DeviceIdleController.
-            if (callingUid != Process.SYSTEM_UID) {
-                flags &= ~AlarmManager.FLAG_IDLE_UNTIL;
-            }
-
             // If this is an exact time alarm, then it can't be batched with other alarms.
-            if (windowLength == AlarmManager.WINDOW_EXACT) {
+            if (exact) {
                 flags |= AlarmManager.FLAG_STANDALONE;
             }
 
-            // If this alarm is for an alarm clock, then it must be standalone and we will
-            // use it to wake early from idle if needed.
-            if (alarmClock != null) {
-                flags |= FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
-
-            // If the caller is a core system component or on the user's whitelist, and not calling
-            // to do work on behalf of someone else, then always set ALLOW_WHILE_IDLE_UNRESTRICTED.
-            // This means we will allow these alarms to go off as normal even while idle, with no
-            // timing restrictions.
-            } else if (workSource == null && (UserHandle.isCore(callingUid)
-                    || UserHandle.isSameApp(callingUid, mSystemUiUid)
-                    || ((mAppStateTracker != null)
-                        && mAppStateTracker.isUidPowerSaveUserExempt(callingUid)))) {
-                flags |= FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
-                flags &= ~FLAG_ALLOW_WHILE_IDLE;
-                flags &= ~FLAG_ALLOW_WHILE_IDLE_COMPAT;
-                flags &= ~FLAG_PRIORITIZE;
-                idleOptions = null;
-            }
-
             setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver,
                     listenerTag, flags, workSource, alarmClock, callingUid, callingPackage,
                     idleOptions);
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java
index c37d2c3..9b1b066 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java
@@ -16,7 +16,6 @@
 
 package com.android.server.alarm;
 
-import static com.android.server.alarm.AlarmManagerService.TAG;
 import static com.android.server.alarm.AlarmManagerService.dumpAlarmList;
 import static com.android.server.alarm.AlarmManagerService.isTimeTickAlarm;
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index d94d638..131783f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -16,6 +16,7 @@
 
 package com.android.server.job;
 
+import static com.android.server.job.JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
 import android.annotation.IntDef;
@@ -39,7 +40,9 @@
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.Pair;
+import android.util.Pools;
 import android.util.Slog;
+import android.util.SparseArrayMap;
 import android.util.SparseIntArray;
 import android.util.SparseLongArray;
 import android.util.TimeUtils;
@@ -60,6 +63,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Iterator;
 import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * This class decides, given the various configuration and the system status, which jobs can start
@@ -73,6 +77,12 @@
     private static final String KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS =
             CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms";
     private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000;
+    private static final String KEY_PKG_CONCURRENCY_LIMIT_EJ =
+            CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_ej";
+    private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_EJ = 3;
+    private static final String KEY_PKG_CONCURRENCY_LIMIT_REGULAR =
+            CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_regular";
+    private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR = MAX_JOB_CONTEXTS_COUNT / 2;
 
     /**
      * Set of possible execution types that a job can have. The actual type(s) of a job are based
@@ -165,8 +175,6 @@
     private long mLastScreenOnRealtime;
     private long mLastScreenOffRealtime;
 
-    private static final int MAX_JOB_CONTEXTS_COUNT = JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
-
     private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_ON =
             new WorkConfigLimitsPerMemoryTrimLevel(
                     new WorkTypeConfig("screen_on_normal", 11,
@@ -274,11 +282,28 @@
 
     private final WorkCountTracker mWorkCountTracker = new WorkCountTracker();
 
+    private final Pools.Pool<PackageStats> mPkgStatsPool =
+            new Pools.SimplePool<>(MAX_JOB_CONTEXTS_COUNT);
+
+    private final SparseArrayMap<String, PackageStats> mActivePkgStats = new SparseArrayMap<>();
+
     private WorkTypeConfig mWorkTypeConfig = CONFIG_LIMITS_SCREEN_OFF.normal;
 
     /** Wait for this long after screen off before adjusting the job concurrency. */
     private long mScreenOffAdjustmentDelayMs = DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS;
 
+    /**
+     * The maximum number of expedited jobs a single userId-package can have running simultaneously.
+     * TOP apps are not limited.
+     */
+    private long mPkgConcurrencyLimitEj = DEFAULT_PKG_CONCURRENCY_LIMIT_EJ;
+
+    /**
+     * The maximum number of regular jobs a single userId-package can have running simultaneously.
+     * TOP apps are not limited.
+     */
+    private long mPkgConcurrencyLimitRegular = DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR;
+
     /** Current memory trim level. */
     private int mLastMemoryTrimLevel;
 
@@ -286,6 +311,9 @@
     private long mNextSystemStateRefreshTime;
     private static final int SYSTEM_STATE_REFRESH_MIN_INTERVAL = 1000;
 
+    private final Consumer<PackageStats> mPackageStatsStagingCountClearer =
+            PackageStats::resetStagedCount;
+
     private final StatLogger mStatLogger = new StatLogger(new String[]{
             "assignJobsToContexts",
             "refreshSystemState",
@@ -330,6 +358,21 @@
         onInteractiveStateChanged(mPowerManager.isInteractive());
     }
 
+    @GuardedBy("mLock")
+    void onAppRemovedLocked(String pkgName, int uid) {
+        final PackageStats packageStats = mActivePkgStats.get(UserHandle.getUserId(uid), pkgName);
+        if (packageStats != null) {
+            if (packageStats.numRunningEj > 0 || packageStats.numRunningRegular > 0) {
+                // Don't delete the object just yet. We'll remove it in onJobCompleted() when the
+                // jobs officially stop running.
+                Slog.w(TAG,
+                        pkgName + "(" + uid + ") marked as removed before jobs stopped running");
+            } else {
+                mActivePkgStats.delete(UserHandle.getUserId(uid), pkgName);
+            }
+        }
+    }
+
     void onUserRemoved(int userId) {
         mGracePeriodObserver.onUserRemoved(userId);
     }
@@ -557,6 +600,7 @@
             boolean startingJob = false;
             int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
             String preemptReason = null;
+            final boolean pkgConcurrencyOkay = !isPkgConcurrencyLimitedLocked(nextPending);
             // TODO(141645789): rewrite this to look at empty contexts first so we don't
             // unnecessarily preempt
             for (int j = 0; j < MAX_JOB_CONTEXTS_COUNT; j++) {
@@ -566,7 +610,7 @@
                     final boolean preferredUidOkay = (preferredUid == nextPending.getUid())
                             || (preferredUid == JobServiceContext.NO_PREFERRED_UID);
 
-                    if (preferredUidOkay && workType != WORK_TYPE_NONE) {
+                    if (preferredUidOkay && pkgConcurrencyOkay && workType != WORK_TYPE_NONE) {
                         // This slot is free, and we haven't yet hit the limit on
                         // concurrent jobs...  we can just throw the job in to here.
                         selectedContextId = j;
@@ -579,9 +623,11 @@
                     continue;
                 }
                 if (job.getUid() != nextPending.getUid()) {
-                    // Maybe stop the job if it has had its day in the sun.
+                    // Maybe stop the job if it has had its day in the sun. Don't let a different
+                    // app preempt jobs started for TOP apps though.
                     final String reason = shouldStopJobReason[j];
-                    if (reason != null && mWorkCountTracker.canJobStart(allWorkTypes,
+                    if (job.lastEvaluatedPriority < JobInfo.PRIORITY_TOP_APP
+                            && reason != null && mWorkCountTracker.canJobStart(allWorkTypes,
                             activeServices.get(j).getRunningJobWorkType()) != WORK_TYPE_NONE) {
                         // Right now, the way the code is set up, we don't need to explicitly
                         // assign the new job to this context since we'll reassign when the
@@ -608,23 +654,27 @@
                     // actually starting a job, so don't set startingJob.
                 }
             }
+            final PackageStats packageStats = getPkgStatsLocked(
+                    nextPending.getSourceUserId(), nextPending.getSourcePackageName());
             if (selectedContextId != -1) {
                 contextIdToJobMap[selectedContextId] = nextPending;
                 slotChanged[selectedContextId] = true;
                 preemptReasonCodeForContext[selectedContextId] = preemptReasonCode;
                 preemptReasonForContext[selectedContextId] = preemptReason;
+                packageStats.adjustStagedCount(true, nextPending.shouldTreatAsExpeditedJob());
             }
             if (startingJob) {
                 // Increase the counters when we're going to start a job.
                 workTypeForContext[selectedContextId] = workType;
                 mWorkCountTracker.stageJob(workType, allWorkTypes);
+                mActivePkgStats.add(
+                        nextPending.getSourceUserId(), nextPending.getSourcePackageName(),
+                        packageStats);
             }
         }
         if (DEBUG) {
             Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
-        }
 
-        if (DEBUG) {
             Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString());
         }
 
@@ -660,6 +710,7 @@
             }
         }
         mWorkCountTracker.resetStagingCount();
+        mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
         noteConcurrency();
     }
 
@@ -702,18 +753,66 @@
     }
 
     @GuardedBy("mLock")
+    @NonNull
+    private PackageStats getPkgStatsLocked(int userId, @NonNull String packageName) {
+        PackageStats packageStats = mActivePkgStats.get(userId, packageName);
+        if (packageStats == null) {
+            packageStats = mPkgStatsPool.acquire();
+            if (packageStats == null) {
+                packageStats = new PackageStats();
+            }
+            packageStats.setPackage(userId, packageName);
+        }
+        return packageStats;
+    }
+
+    @GuardedBy("mLock")
+    private boolean isPkgConcurrencyLimitedLocked(@NonNull JobStatus jobStatus) {
+        if (jobStatus.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
+            // Don't restrict top apps' concurrency. The work type limits will make sure
+            // background jobs have slots to run if the system has resources.
+            return false;
+        }
+        // Use < instead of <= as that gives us a little wiggle room in case a new job comes
+        // along very shortly.
+        if (mService.mPendingJobs.size() + mRunningJobs.size() < mWorkTypeConfig.getMaxTotal()) {
+            // Don't artificially limit a single package if we don't even have enough jobs to use
+            // the maximum number of slots. We'll preempt the job later if we need the slot.
+            return false;
+        }
+        final PackageStats packageStats =
+                mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
+        if (packageStats == null) {
+            // No currently running jobs.
+            return false;
+        }
+        if (jobStatus.shouldTreatAsExpeditedJob()) {
+            return packageStats.numRunningEj + packageStats.numStagedEj < mPkgConcurrencyLimitEj;
+        } else {
+            return packageStats.numRunningRegular + packageStats.numStagedRegular
+                    < mPkgConcurrencyLimitRegular;
+        }
+    }
+
+    @GuardedBy("mLock")
     private void startJobLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus,
             @WorkType final int workType) {
         final List<StateController> controllers = mService.mControllers;
         for (int ic = 0; ic < controllers.size(); ic++) {
             controllers.get(ic).prepareForExecutionLocked(jobStatus);
         }
+        final PackageStats packageStats =
+                getPkgStatsLocked(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
+        packageStats.adjustStagedCount(false, jobStatus.shouldTreatAsExpeditedJob());
         if (!worker.executeRunnableJob(jobStatus, workType)) {
             Slog.e(TAG, "Error executing " + jobStatus);
             mWorkCountTracker.onStagedJobFailed(workType);
         } else {
             mRunningJobs.add(jobStatus);
             mWorkCountTracker.onJobStarted(workType);
+            packageStats.adjustRunningCount(true, jobStatus.shouldTreatAsExpeditedJob());
+            mActivePkgStats.add(
+                    jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), packageStats);
         }
         final List<JobStatus> pendingJobs = mService.mPendingJobs;
         if (pendingJobs.remove(jobStatus)) {
@@ -726,6 +825,18 @@
             @WorkType final int workType) {
         mWorkCountTracker.onJobFinished(workType);
         mRunningJobs.remove(jobStatus);
+        final PackageStats packageStats =
+                mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
+        if (packageStats == null) {
+            Slog.wtf(TAG, "Running job didn't have an active PackageStats object");
+        } else {
+            packageStats.adjustRunningCount(false, jobStatus.startedAsExpeditedJob);
+            if (packageStats.numRunningEj <= 0 && packageStats.numRunningRegular <= 0) {
+                mActivePkgStats.delete(packageStats.userId, packageStats.packageName);
+                mPkgStatsPool.release(packageStats);
+            }
+        }
+
         final List<JobStatus> pendingJobs = mService.mPendingJobs;
         if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) {
             updateCounterConfigLocked();
@@ -746,7 +857,7 @@
                 }
 
                 if (worker.getPreferredUid() != nextPending.getUid()) {
-                    if (backupJob == null) {
+                    if (backupJob == null && !isPkgConcurrencyLimitedLocked(nextPending)) {
                         int allWorkTypes = getJobWorkTypes(nextPending);
                         int workAsType = mWorkCountTracker.canJobStart(allWorkTypes);
                         if (workAsType != WORK_TYPE_NONE) {
@@ -758,6 +869,13 @@
                     continue;
                 }
 
+                // Only bypass the concurrent limit if we had preempted the job due to a higher
+                // priority job.
+                if (nextPending.lastEvaluatedPriority <= jobStatus.lastEvaluatedPriority
+                        && isPkgConcurrencyLimitedLocked(nextPending)) {
+                    continue;
+                }
+
                 if (highestPriorityJob == null
                         || highestPriorityJob.lastEvaluatedPriority
                         < nextPending.lastEvaluatedPriority) {
@@ -815,6 +933,10 @@
                     continue;
                 }
 
+                if (isPkgConcurrencyLimitedLocked(nextPending)) {
+                    continue;
+                }
+
                 final int allWorkTypes = getJobWorkTypes(nextPending);
                 final int workAsType = mWorkCountTracker.canJobStart(allWorkTypes);
                 if (workAsType == WORK_TYPE_NONE) {
@@ -979,8 +1101,16 @@
         CONFIG_LIMITS_SCREEN_OFF.moderate.update(properties);
         CONFIG_LIMITS_SCREEN_OFF.low.update(properties);
         CONFIG_LIMITS_SCREEN_OFF.critical.update(properties);
+
+        // Package concurrency limits must in the range [1, MAX_JOB_CONTEXTS_COUNT].
+        mPkgConcurrencyLimitEj = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT,
+                properties.getInt(KEY_PKG_CONCURRENCY_LIMIT_EJ, DEFAULT_PKG_CONCURRENCY_LIMIT_EJ)));
+        mPkgConcurrencyLimitRegular = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT,
+                properties.getInt(
+                        KEY_PKG_CONCURRENCY_LIMIT_REGULAR, DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR)));
     }
 
+    @GuardedBy("mLock")
     public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) {
         pw.println("Concurrency:");
 
@@ -989,6 +1119,8 @@
             pw.println("Configuration:");
             pw.increaseIndent();
             pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println();
+            pw.print(KEY_PKG_CONCURRENCY_LIMIT_EJ, mPkgConcurrencyLimitEj).println();
+            pw.print(KEY_PKG_CONCURRENCY_LIMIT_REGULAR, mPkgConcurrencyLimitRegular).println();
             pw.println();
             CONFIG_LIMITS_SCREEN_ON.normal.dump(pw);
             pw.println();
@@ -1033,6 +1165,12 @@
             pw.println(mLastMemoryTrimLevel);
             pw.println();
 
+            pw.println("Active Package stats:");
+            pw.increaseIndent();
+            mActivePkgStats.forEach(pkgStats -> pkgStats.dumpLocked(pw));
+            pw.decreaseIndent();
+            pw.println();
+
             pw.print("User Grace Period: ");
             pw.println(mGracePeriodObserver.mGracePeriodExpiration);
             pw.println();
@@ -1620,4 +1758,53 @@
             return sb.toString();
         }
     }
+
+    private static class PackageStats {
+        public int userId;
+        public String packageName;
+        public int numRunningEj;
+        public int numRunningRegular;
+        public int numStagedEj;
+        public int numStagedRegular;
+
+        private void setPackage(int userId, @NonNull String packageName) {
+            this.userId = userId;
+            this.packageName = packageName;
+            numRunningEj = numRunningRegular = 0;
+            resetStagedCount();
+        }
+
+        private void resetStagedCount() {
+            numStagedEj = numStagedRegular = 0;
+        }
+
+        private void adjustRunningCount(boolean add, boolean forEj) {
+            if (forEj) {
+                numRunningEj = Math.max(0, numRunningEj + (add ? 1 : -1));
+            } else {
+                numRunningRegular = Math.max(0, numRunningRegular + (add ? 1 : -1));
+            }
+        }
+
+        private void adjustStagedCount(boolean add, boolean forEj) {
+            if (forEj) {
+                numStagedEj = Math.max(0, numStagedEj + (add ? 1 : -1));
+            } else {
+                numStagedRegular = Math.max(0, numStagedRegular + (add ? 1 : -1));
+            }
+        }
+
+        @GuardedBy("mLock")
+        private void dumpLocked(IndentingPrintWriter pw) {
+            pw.print("PackageStats{");
+            pw.print(userId);
+            pw.print("-");
+            pw.print(packageName);
+            pw.print("#runEJ", numRunningEj);
+            pw.print("#runReg", numRunningRegular);
+            pw.print("#stagedEJ", numStagedEj);
+            pw.print("#stagedReg", numStagedRegular);
+            pw.println("}");
+        }
+    }
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index d72f565b..1815661 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -816,6 +816,7 @@
                         mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid);
                     }
                     mDebuggableApps.remove(pkgName);
+                    mConcurrencyManager.onAppRemovedLocked(pkgName, pkgUid);
                 }
             } else if (Intent.ACTION_USER_ADDED.equals(action)) {
                 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
@@ -2233,7 +2234,8 @@
     }
 
     /** Returns true if both the calling and source users for the job are started. */
-    private boolean areUsersStartedLocked(final JobStatus job) {
+    @GuardedBy("mLock")
+    public boolean areUsersStartedLocked(final JobStatus job) {
         boolean sourceStarted = ArrayUtils.contains(mStartedUsers, job.getSourceUserId());
         if (job.getUserId() == job.getSourceUserId()) {
             return sourceStarted;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
index 999c53f..12d9c7f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
@@ -34,6 +34,7 @@
 import android.util.SparseArrayMap;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.server.job.JobSchedulerService;
 
 import java.util.Objects;
@@ -58,13 +59,28 @@
                 return;
             }
             switch (action) {
+                case Intent.ACTION_PACKAGE_ADDED:
+                    if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                        // Only do this for app updates since new installs won't have any jobs
+                        // scheduled.
+                        final Uri uri = intent.getData();
+                        final String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
+                        if (pkg != null) {
+                            final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+                            final int userId = UserHandle.getUserId(pkgUid);
+                            updateComponentStateForPackage(userId, pkg);
+                        }
+                    }
+                    break;
                 case Intent.ACTION_PACKAGE_CHANGED:
                     final Uri uri = intent.getData();
                     final String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
                     final String[] changedComponents = intent.getStringArrayExtra(
                             Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
                     if (pkg != null && changedComponents != null && changedComponents.length > 0) {
-                        updateComponentStateForPackage(pkg);
+                        final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+                        final int userId = UserHandle.getUserId(pkgUid);
+                        updateComponentStateForPackage(userId, pkg);
                     }
                     break;
                 case Intent.ACTION_USER_UNLOCKED:
@@ -86,6 +102,7 @@
         super(service);
 
         final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
         filter.addDataScheme("package");
         mContext.registerReceiverAsUser(
@@ -99,6 +116,7 @@
     }
 
     @Override
+    @GuardedBy("mLock")
     public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
         updateComponentEnabledStateLocked(jobStatus);
     }
@@ -108,30 +126,53 @@
             boolean forUpdate) {
     }
 
+    @Override
+    @GuardedBy("mLock")
+    public void onAppRemovedLocked(String packageName, int uid) {
+        clearComponentsForPackageLocked(UserHandle.getUserId(uid), packageName);
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    public void onUserRemovedLocked(int userId) {
+        mServiceInfoCache.delete(userId);
+    }
+
     @Nullable
-    private ServiceInfo getServiceInfo(JobStatus jobStatus) {
+    @GuardedBy("mLock")
+    private ServiceInfo getServiceInfoLocked(JobStatus jobStatus) {
         final ComponentName service = jobStatus.getServiceComponent();
         final int userId = jobStatus.getUserId();
-        ServiceInfo si = mServiceInfoCache.get(userId, service);
-        if (si == null) {
-            try {
-                // createContextAsUser may potentially be expensive
-                // TODO: cache user context or improve ContextImpl implementation if this becomes
-                // a problem
-                si = mContext.createContextAsUser(UserHandle.of(userId), 0)
-                        .getPackageManager()
-                        .getServiceInfo(service, PackageManager.MATCH_DIRECT_BOOT_AUTO);
-            } catch (NameNotFoundException e) {
-                Slog.e(TAG, "Job exists for non-existent package: " + service.getPackageName());
-                return null;
-            }
-            mServiceInfoCache.add(userId, service, si);
+        if (mServiceInfoCache.contains(userId, service)) {
+            // Return whatever is in the cache, even if it's null. When something changes, we
+            // clear the cache.
+            return mServiceInfoCache.get(userId, service);
         }
+
+        ServiceInfo si;
+        try {
+            // createContextAsUser may potentially be expensive
+            // TODO: cache user context or improve ContextImpl implementation if this becomes
+            // a problem
+            si = mContext.createContextAsUser(UserHandle.of(userId), 0)
+                    .getPackageManager()
+                    .getServiceInfo(service, PackageManager.MATCH_DIRECT_BOOT_AUTO);
+        } catch (NameNotFoundException e) {
+            if (mService.areUsersStartedLocked(jobStatus)) {
+                // User is fully unlocked but PM still says the package doesn't exist.
+                Slog.e(TAG, "Job exists for non-existent package: " + service.getPackageName());
+            }
+            // Write null to the cache so we don't keep querying PM.
+            si = null;
+        }
+        mServiceInfoCache.add(userId, service, si);
+
         return si;
     }
 
+    @GuardedBy("mLock")
     private boolean updateComponentEnabledStateLocked(JobStatus jobStatus) {
-        final ServiceInfo service = getServiceInfo(jobStatus);
+        final ServiceInfo service = getServiceInfoLocked(jobStatus);
 
         if (DEBUG && service == null) {
             Slog.v(TAG, jobStatus.toShortString() + " component not present");
@@ -141,20 +182,26 @@
         return !Objects.equals(ogService, service);
     }
 
-    private void updateComponentStateForPackage(final String pkg) {
-        synchronized (mLock) {
-            for (int u = mServiceInfoCache.numMaps() - 1; u >= 0; --u) {
-                final int userId = mServiceInfoCache.keyAt(u);
-
-                for (int c = mServiceInfoCache.numElementsForKey(userId) - 1; c >= 0; --c) {
-                    final ComponentName cn = mServiceInfoCache.keyAt(u, c);
-                    if (cn.getPackageName().equals(pkg)) {
-                        mServiceInfoCache.delete(userId, cn);
-                    }
-                }
+    @GuardedBy("mLock")
+    private void clearComponentsForPackageLocked(final int userId, final String pkg) {
+        final int uIdx = mServiceInfoCache.indexOfKey(userId);
+        for (int c = mServiceInfoCache.numElementsForKey(userId) - 1; c >= 0; --c) {
+            final ComponentName cn = mServiceInfoCache.keyAt(uIdx, c);
+            if (cn.getPackageName().equals(pkg)) {
+                mServiceInfoCache.delete(userId, cn);
             }
-            updateComponentStatesLocked(
-                    jobStatus -> jobStatus.getServiceComponent().getPackageName().equals(pkg));
+        }
+    }
+
+    private void updateComponentStateForPackage(final int userId, final String pkg) {
+        synchronized (mLock) {
+            clearComponentsForPackageLocked(userId, pkg);
+            updateComponentStatesLocked(jobStatus -> {
+                // Using user ID instead of source user ID because the service will run under the
+                // user ID, not source user ID.
+                return jobStatus.getUserId() == userId
+                        && jobStatus.getServiceComponent().getPackageName().equals(pkg);
+            });
         }
     }
 
@@ -169,6 +216,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void updateComponentStatesLocked(@NonNull Predicate<JobStatus> filter) {
         mComponentStateUpdateFunctor.reset();
         mService.getJobStore().forEachJob(filter, mComponentStateUpdateFunctor);
@@ -178,24 +226,40 @@
     }
 
     final class ComponentStateUpdateFunctor implements Consumer<JobStatus> {
+        @GuardedBy("mLock")
         boolean mChanged;
 
         @Override
+        @GuardedBy("mLock")
         public void accept(JobStatus jobStatus) {
             mChanged |= updateComponentEnabledStateLocked(jobStatus);
         }
 
+        @GuardedBy("mLock")
         private void reset() {
             mChanged = false;
         }
     }
 
     @Override
+    @GuardedBy("mLock")
     public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
-
+        for (int u = 0; u < mServiceInfoCache.numMaps(); ++u) {
+            final int userId = mServiceInfoCache.keyAt(u);
+            for (int p = 0; p < mServiceInfoCache.numElementsForKey(userId); ++p) {
+                final ComponentName componentName = mServiceInfoCache.keyAt(u, p);
+                pw.print(userId);
+                pw.print("-");
+                pw.print(componentName);
+                pw.print(": ");
+                pw.print(mServiceInfoCache.valueAt(u, p));
+                pw.println();
+            }
+        }
     }
 
     @Override
+    @GuardedBy("mLock")
     public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
             Predicate<JobStatus> predicate) {
 
diff --git a/apex/media/framework/api/current.txt b/apex/media/framework/api/current.txt
index bebf019..b7d7ed8 100644
--- a/apex/media/framework/api/current.txt
+++ b/apex/media/framework/api/current.txt
@@ -10,7 +10,6 @@
     method @NonNull public java.util.List<java.lang.String> getUnsupportedVideoMimeTypes();
     method public boolean isFormatSpecified(@NonNull String);
     method public boolean isHdrTypeSupported(@NonNull String);
-    method public boolean isSlowMotionSupported();
     method public boolean isVideoMimeTypeSupported(@NonNull String);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.ApplicationMediaCapabilities> CREATOR;
diff --git a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
index 3f30d3e..97fa0ec 100644
--- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
+++ b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
@@ -242,7 +242,7 @@
                 }
             };
 
-    /*
+    /**
      * Query the video codec mime types supported by the application.
      * @return List of supported video codec mime types. The list will be empty if there are none.
      */
@@ -251,7 +251,7 @@
         return new ArrayList<>(mSupportedVideoMimeTypes);
     }
 
-    /*
+    /**
      * Query the video codec mime types that are not supported by the application.
      * @return List of unsupported video codec mime types. The list will be empty if there are none.
      */
@@ -260,7 +260,7 @@
         return new ArrayList<>(mUnsupportedVideoMimeTypes);
     }
 
-    /*
+    /**
      * Query all hdr types that are supported by the application.
      * @return List of supported hdr types. The list will be empty if there are none.
      */
@@ -269,7 +269,7 @@
         return new ArrayList<>(mSupportedHdrTypes);
     }
 
-    /*
+    /**
      * Query all hdr types that are not supported by the application.
      * @return List of unsupported hdr types. The list will be empty if there are none.
      */
@@ -278,7 +278,7 @@
         return new ArrayList<>(mUnsupportedHdrTypes);
     }
 
-    /*
+    /**
      * Whether handling of slow-motion video is supported
      * @hide
      */
diff --git a/core/api/current.txt b/core/api/current.txt
index 4b12d54..0420714 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8604,37 +8604,37 @@
 
   public final class BluetoothA2dp implements android.bluetooth.BluetoothProfile {
     method public void finalize();
-    method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method public int getConnectionState(android.bluetooth.BluetoothDevice);
-    method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
-    method public boolean isA2dpPlaying(android.bluetooth.BluetoothDevice);
-    field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED";
-    field public static final String ACTION_PLAYING_STATE_CHANGED = "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED";
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isA2dpPlaying(android.bluetooth.BluetoothDevice);
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_PLAYING_STATE_CHANGED = "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED";
     field public static final int STATE_NOT_PLAYING = 11; // 0xb
     field public static final int STATE_PLAYING = 10; // 0xa
   }
 
   public final class BluetoothAdapter {
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean cancelDiscovery();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean cancelDiscovery();
     method public static boolean checkBluetoothAddress(String);
     method public void closeProfileProxy(int, android.bluetooth.BluetoothProfile);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean disable();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean enable();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public String getAddress();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disable();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean enable();
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, "android.permission.LOCAL_MAC_ADDRESS"}) public String getAddress();
     method public android.bluetooth.le.BluetoothLeAdvertiser getBluetoothLeAdvertiser();
     method public android.bluetooth.le.BluetoothLeScanner getBluetoothLeScanner();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public java.util.Set<android.bluetooth.BluetoothDevice> getBondedDevices();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.Set<android.bluetooth.BluetoothDevice> getBondedDevices();
     method public static android.bluetooth.BluetoothAdapter getDefaultAdapter();
     method public int getLeMaximumAdvertisingDataLength();
-    method public String getName();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getProfileConnectionState(int);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public String getName();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getProfileConnectionState(int);
     method public boolean getProfileProxy(android.content.Context, android.bluetooth.BluetoothProfile.ServiceListener, int);
     method public android.bluetooth.BluetoothDevice getRemoteDevice(String);
     method public android.bluetooth.BluetoothDevice getRemoteDevice(byte[]);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getScanMode();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getState();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isDiscovering();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEnabled();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public int getScanMode();
+    method public int getState();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean isDiscovering();
+    method public boolean isEnabled();
     method public boolean isLe2MPhySupported();
     method public boolean isLeCodedPhySupported();
     method public boolean isLeExtendedAdvertisingSupported();
@@ -8642,22 +8642,22 @@
     method public boolean isMultipleAdvertisementSupported();
     method public boolean isOffloadedFilteringSupported();
     method public boolean isOffloadedScanBatchingSupported();
-    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothServerSocket listenUsingInsecureL2capChannel() throws java.io.IOException;
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(String, java.util.UUID) throws java.io.IOException;
-    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothServerSocket listenUsingL2capChannel() throws java.io.IOException;
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothServerSocket listenUsingRfcommWithServiceRecord(String, java.util.UUID) throws java.io.IOException;
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setName(String);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean startDiscovery();
-    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean startLeScan(android.bluetooth.BluetoothAdapter.LeScanCallback);
-    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean startLeScan(java.util.UUID[], android.bluetooth.BluetoothAdapter.LeScanCallback);
-    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public void stopLeScan(android.bluetooth.BluetoothAdapter.LeScanCallback);
-    field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED";
-    field public static final String ACTION_DISCOVERY_FINISHED = "android.bluetooth.adapter.action.DISCOVERY_FINISHED";
-    field public static final String ACTION_DISCOVERY_STARTED = "android.bluetooth.adapter.action.DISCOVERY_STARTED";
-    field public static final String ACTION_LOCAL_NAME_CHANGED = "android.bluetooth.adapter.action.LOCAL_NAME_CHANGED";
-    field public static final String ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";
-    field public static final String ACTION_REQUEST_ENABLE = "android.bluetooth.adapter.action.REQUEST_ENABLE";
-    field public static final String ACTION_SCAN_MODE_CHANGED = "android.bluetooth.adapter.action.SCAN_MODE_CHANGED";
+    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothServerSocket listenUsingInsecureL2capChannel() throws java.io.IOException;
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(String, java.util.UUID) throws java.io.IOException;
+    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothServerSocket listenUsingL2capChannel() throws java.io.IOException;
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothServerSocket listenUsingRfcommWithServiceRecord(String, java.util.UUID) throws java.io.IOException;
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setName(String);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean startDiscovery();
+    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean startLeScan(android.bluetooth.BluetoothAdapter.LeScanCallback);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean startLeScan(java.util.UUID[], android.bluetooth.BluetoothAdapter.LeScanCallback);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void stopLeScan(android.bluetooth.BluetoothAdapter.LeScanCallback);
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public static final String ACTION_DISCOVERY_FINISHED = "android.bluetooth.adapter.action.DISCOVERY_FINISHED";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public static final String ACTION_DISCOVERY_STARTED = "android.bluetooth.adapter.action.DISCOVERY_STARTED";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_LOCAL_NAME_CHANGED = "android.bluetooth.adapter.action.LOCAL_NAME_CHANGED";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public static final String ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_REQUEST_ENABLE = "android.bluetooth.adapter.action.REQUEST_ENABLE";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public static final String ACTION_SCAN_MODE_CHANGED = "android.bluetooth.adapter.action.SCAN_MODE_CHANGED";
     field public static final String ACTION_STATE_CHANGED = "android.bluetooth.adapter.action.STATE_CHANGED";
     field public static final int ERROR = -2147483648; // 0x80000000
     field public static final String EXTRA_CONNECTION_STATE = "android.bluetooth.adapter.extra.CONNECTION_STATE";
@@ -9007,38 +9007,38 @@
   }
 
   public final class BluetoothDevice implements android.os.Parcelable {
-    method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback);
-    method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int);
-    method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int, int);
-    method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int, int, android.os.Handler);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean createBond();
-    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothSocket createInsecureL2capChannel(int) throws java.io.IOException;
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothSocket createInsecureRfcommSocketToServiceRecord(java.util.UUID) throws java.io.IOException;
-    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothSocket createL2capChannel(int) throws java.io.IOException;
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothSocket createRfcommSocketToServiceRecord(java.util.UUID) throws java.io.IOException;
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int, int);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int, int, android.os.Handler);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean createBond();
+    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothSocket createInsecureL2capChannel(int) throws java.io.IOException;
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothSocket createInsecureRfcommSocketToServiceRecord(java.util.UUID) throws java.io.IOException;
+    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothSocket createL2capChannel(int) throws java.io.IOException;
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothSocket createRfcommSocketToServiceRecord(java.util.UUID) throws java.io.IOException;
     method public int describeContents();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean fetchUuidsWithSdp();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean fetchUuidsWithSdp();
     method public String getAddress();
-    method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH) public String getAlias();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothClass getBluetoothClass();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getBondState();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public String getName();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getType();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.os.ParcelUuid[] getUuids();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean setAlias(@NonNull String);
+    method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public String getAlias();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothClass getBluetoothClass();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getBondState();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public String getName();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getType();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.os.ParcelUuid[] getUuids();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setAlias(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPairingConfirmation(boolean);
-    method public boolean setPin(byte[]);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setPin(byte[]);
     method public void writeToParcel(android.os.Parcel, int);
-    field public static final String ACTION_ACL_CONNECTED = "android.bluetooth.device.action.ACL_CONNECTED";
-    field public static final String ACTION_ACL_DISCONNECTED = "android.bluetooth.device.action.ACL_DISCONNECTED";
-    field public static final String ACTION_ACL_DISCONNECT_REQUESTED = "android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED";
-    field public static final String ACTION_ALIAS_CHANGED = "android.bluetooth.device.action.ALIAS_CHANGED";
-    field public static final String ACTION_BOND_STATE_CHANGED = "android.bluetooth.device.action.BOND_STATE_CHANGED";
-    field public static final String ACTION_CLASS_CHANGED = "android.bluetooth.device.action.CLASS_CHANGED";
-    field public static final String ACTION_FOUND = "android.bluetooth.device.action.FOUND";
-    field public static final String ACTION_NAME_CHANGED = "android.bluetooth.device.action.NAME_CHANGED";
-    field public static final String ACTION_PAIRING_REQUEST = "android.bluetooth.device.action.PAIRING_REQUEST";
-    field public static final String ACTION_UUID = "android.bluetooth.device.action.UUID";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_ACL_CONNECTED = "android.bluetooth.device.action.ACL_CONNECTED";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_ACL_DISCONNECTED = "android.bluetooth.device.action.ACL_DISCONNECTED";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_ACL_DISCONNECT_REQUESTED = "android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_ALIAS_CHANGED = "android.bluetooth.device.action.ALIAS_CHANGED";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_BOND_STATE_CHANGED = "android.bluetooth.device.action.BOND_STATE_CHANGED";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CLASS_CHANGED = "android.bluetooth.device.action.CLASS_CHANGED";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public static final String ACTION_FOUND = "android.bluetooth.device.action.FOUND";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_NAME_CHANGED = "android.bluetooth.device.action.NAME_CHANGED";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public static final String ACTION_PAIRING_REQUEST = "android.bluetooth.device.action.PAIRING_REQUEST";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public static final String ACTION_UUID = "android.bluetooth.device.action.UUID";
     field public static final int ADDRESS_TYPE_PUBLIC = 0; // 0x0
     field public static final int ADDRESS_TYPE_RANDOM = 1; // 0x1
     field public static final int BOND_BONDED = 12; // 0xc
@@ -9076,30 +9076,30 @@
   }
 
   public final class BluetoothGatt implements android.bluetooth.BluetoothProfile {
-    method public void abortReliableWrite();
-    method @Deprecated public void abortReliableWrite(android.bluetooth.BluetoothDevice);
-    method public boolean beginReliableWrite();
-    method public void close();
-    method public boolean connect();
-    method public void disconnect();
-    method public boolean discoverServices();
-    method public boolean executeReliableWrite();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void abortReliableWrite();
+    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void abortReliableWrite(android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean beginReliableWrite();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void close();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean connect();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void disconnect();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean discoverServices();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean executeReliableWrite();
     method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
     method public int getConnectionState(android.bluetooth.BluetoothDevice);
     method public android.bluetooth.BluetoothDevice getDevice();
     method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
     method public android.bluetooth.BluetoothGattService getService(java.util.UUID);
     method public java.util.List<android.bluetooth.BluetoothGattService> getServices();
-    method public boolean readCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
-    method public boolean readDescriptor(android.bluetooth.BluetoothGattDescriptor);
-    method public void readPhy();
-    method public boolean readRemoteRssi();
-    method public boolean requestConnectionPriority(int);
-    method public boolean requestMtu(int);
-    method public boolean setCharacteristicNotification(android.bluetooth.BluetoothGattCharacteristic, boolean);
-    method public void setPreferredPhy(int, int, int);
-    method public boolean writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
-    method public boolean writeDescriptor(android.bluetooth.BluetoothGattDescriptor);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean readCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean readDescriptor(android.bluetooth.BluetoothGattDescriptor);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void readPhy();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean readRemoteRssi();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean requestConnectionPriority(int);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean requestMtu(int);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setCharacteristicNotification(android.bluetooth.BluetoothGattCharacteristic, boolean);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void setPreferredPhy(int, int, int);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean writeDescriptor(android.bluetooth.BluetoothGattDescriptor);
     field public static final int CONNECTION_PRIORITY_BALANCED = 0; // 0x0
     field public static final int CONNECTION_PRIORITY_HIGH = 1; // 0x1
     field public static final int CONNECTION_PRIORITY_LOW_POWER = 2; // 0x2
@@ -9209,21 +9209,21 @@
   }
 
   public final class BluetoothGattServer implements android.bluetooth.BluetoothProfile {
-    method public boolean addService(android.bluetooth.BluetoothGattService);
-    method public void cancelConnection(android.bluetooth.BluetoothDevice);
-    method public void clearServices();
-    method public void close();
-    method public boolean connect(android.bluetooth.BluetoothDevice, boolean);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean addService(android.bluetooth.BluetoothGattService);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void cancelConnection(android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void clearServices();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void close();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean connect(android.bluetooth.BluetoothDevice, boolean);
     method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
     method public int getConnectionState(android.bluetooth.BluetoothDevice);
     method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
     method public android.bluetooth.BluetoothGattService getService(java.util.UUID);
     method public java.util.List<android.bluetooth.BluetoothGattService> getServices();
-    method public boolean notifyCharacteristicChanged(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothGattCharacteristic, boolean);
-    method public void readPhy(android.bluetooth.BluetoothDevice);
-    method public boolean removeService(android.bluetooth.BluetoothGattService);
-    method public boolean sendResponse(android.bluetooth.BluetoothDevice, int, int, int, byte[]);
-    method public void setPreferredPhy(android.bluetooth.BluetoothDevice, int, int, int);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean notifyCharacteristicChanged(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothGattCharacteristic, boolean);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void readPhy(android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean removeService(android.bluetooth.BluetoothGattService);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean sendResponse(android.bluetooth.BluetoothDevice, int, int, int, byte[]);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void setPreferredPhy(android.bluetooth.BluetoothDevice, int, int, int);
   }
 
   public abstract class BluetoothGattServerCallback {
@@ -9261,18 +9261,18 @@
   }
 
   public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
-    method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method public int getConnectionState(android.bluetooth.BluetoothDevice);
-    method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
-    method public boolean isAudioConnected(android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isNoiseReductionSupported(@NonNull android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isVoiceRecognitionSupported(@NonNull android.bluetooth.BluetoothDevice);
-    method public boolean sendVendorSpecificResultCode(android.bluetooth.BluetoothDevice, String, String);
-    method public boolean startVoiceRecognition(android.bluetooth.BluetoothDevice);
-    method public boolean stopVoiceRecognition(android.bluetooth.BluetoothDevice);
-    field public static final String ACTION_AUDIO_STATE_CHANGED = "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
-    field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
-    field public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT = "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isAudioConnected(android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isNoiseReductionSupported(@NonNull android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isVoiceRecognitionSupported(@NonNull android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean sendVendorSpecificResultCode(android.bluetooth.BluetoothDevice, String, String);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean startVoiceRecognition(android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean stopVoiceRecognition(android.bluetooth.BluetoothDevice);
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_AUDIO_STATE_CHANGED = "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT = "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
     field public static final int AT_CMD_TYPE_ACTION = 4; // 0x4
     field public static final int AT_CMD_TYPE_BASIC = 3; // 0x3
     field public static final int AT_CMD_TYPE_READ = 0; // 0x0
@@ -9289,14 +9289,14 @@
   }
 
   @Deprecated public final class BluetoothHealth implements android.bluetooth.BluetoothProfile {
-    method @Deprecated public boolean connectChannelToSource(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration);
-    method @Deprecated public boolean disconnectChannel(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration, int);
-    method @Deprecated public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method @Deprecated public int getConnectionState(android.bluetooth.BluetoothDevice);
-    method @Deprecated public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
-    method @Deprecated public android.os.ParcelFileDescriptor getMainChannelFd(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration);
-    method @Deprecated public boolean registerSinkAppConfiguration(String, int, android.bluetooth.BluetoothHealthCallback);
-    method @Deprecated public boolean unregisterAppConfiguration(android.bluetooth.BluetoothHealthAppConfiguration);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean connectChannelToSource(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnectChannel(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration, int);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(android.bluetooth.BluetoothDevice);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.os.ParcelFileDescriptor getMainChannelFd(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean registerSinkAppConfiguration(String, int, android.bluetooth.BluetoothHealthCallback);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean unregisterAppConfiguration(android.bluetooth.BluetoothHealthAppConfiguration);
     field @Deprecated public static final int APP_CONFIG_REGISTRATION_FAILURE = 1; // 0x1
     field @Deprecated public static final int APP_CONFIG_REGISTRATION_SUCCESS = 0; // 0x0
     field @Deprecated public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3; // 0x3
@@ -9327,24 +9327,24 @@
   }
 
   public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile {
-    method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
-    method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]);
-    field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED";
+    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
+    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]);
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED";
   }
 
   public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile {
-    method public boolean connect(android.bluetooth.BluetoothDevice);
-    method public boolean disconnect(android.bluetooth.BluetoothDevice);
-    method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method public int getConnectionState(android.bluetooth.BluetoothDevice);
-    method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
-    method public boolean registerApp(android.bluetooth.BluetoothHidDeviceAppSdpSettings, android.bluetooth.BluetoothHidDeviceAppQosSettings, android.bluetooth.BluetoothHidDeviceAppQosSettings, java.util.concurrent.Executor, android.bluetooth.BluetoothHidDevice.Callback);
-    method public boolean replyReport(android.bluetooth.BluetoothDevice, byte, byte, byte[]);
-    method public boolean reportError(android.bluetooth.BluetoothDevice, byte);
-    method public boolean sendReport(android.bluetooth.BluetoothDevice, int, byte[]);
-    method public boolean unregisterApp();
-    field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED";
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean connect(android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnect(android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean registerApp(android.bluetooth.BluetoothHidDeviceAppSdpSettings, android.bluetooth.BluetoothHidDeviceAppQosSettings, android.bluetooth.BluetoothHidDeviceAppQosSettings, java.util.concurrent.Executor, android.bluetooth.BluetoothHidDevice.Callback);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean replyReport(android.bluetooth.BluetoothDevice, byte, byte, byte[]);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean reportError(android.bluetooth.BluetoothDevice, byte);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean sendReport(android.bluetooth.BluetoothDevice, int, byte[]);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean unregisterApp();
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED";
     field public static final byte ERROR_RSP_INVALID_PARAM = 4; // 0x4
     field public static final byte ERROR_RSP_INVALID_RPT_ID = 2; // 0x2
     field public static final byte ERROR_RSP_NOT_READY = 1; // 0x1
@@ -9410,26 +9410,26 @@
   }
 
   public final class BluetoothLeAudio implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void close();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) protected void finalize();
-    method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
-    method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]);
-    field public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED = "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED";
+    method public void close();
+    method protected void finalize();
+    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
+    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]);
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED = "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED";
   }
 
   public final class BluetoothManager {
     method public android.bluetooth.BluetoothAdapter getAdapter();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(int);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionState(android.bluetooth.BluetoothDevice, int);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int, int[]);
-    method public android.bluetooth.BluetoothGattServer openGattServer(android.content.Context, android.bluetooth.BluetoothGattServerCallback);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(int);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(android.bluetooth.BluetoothDevice, int);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int, int[]);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothGattServer openGattServer(android.content.Context, android.bluetooth.BluetoothGattServerCallback);
   }
 
   public interface BluetoothProfile {
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionState(android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
+    method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method public int getConnectionState(android.bluetooth.BluetoothDevice);
+    method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
     field public static final int A2DP = 2; // 0x2
     field public static final String EXTRA_PREVIOUS_STATE = "android.bluetooth.profile.extra.PREVIOUS_STATE";
     field public static final String EXTRA_STATE = "android.bluetooth.profile.extra.STATE";
@@ -9460,7 +9460,7 @@
 
   public final class BluetoothSocket implements java.io.Closeable {
     method public void close() throws java.io.IOException;
-    method public void connect() throws java.io.IOException;
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void connect() throws java.io.IOException;
     method public int getConnectionType();
     method public java.io.InputStream getInputStream() throws java.io.IOException;
     method public int getMaxReceivePacketSize();
@@ -9538,13 +9538,13 @@
   }
 
   public final class AdvertisingSet {
-    method public void enableAdvertising(boolean, int, int);
-    method public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
-    method public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
-    method public void setPeriodicAdvertisingData(android.bluetooth.le.AdvertiseData);
-    method public void setPeriodicAdvertisingEnabled(boolean);
-    method public void setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
-    method public void setScanResponseData(android.bluetooth.le.AdvertiseData);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void enableAdvertising(boolean, int, int);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setPeriodicAdvertisingData(android.bluetooth.le.AdvertiseData);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setPeriodicAdvertisingEnabled(boolean);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setScanResponseData(android.bluetooth.le.AdvertiseData);
   }
 
   public abstract class AdvertisingSetCallback {
@@ -9607,23 +9607,23 @@
   }
 
   public final class BluetoothLeAdvertiser {
-    method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
-    method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
-    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback);
-    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
-    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, int, android.bluetooth.le.AdvertisingSetCallback);
-    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, int, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
-    method public void stopAdvertising(android.bluetooth.le.AdvertiseCallback);
-    method public void stopAdvertisingSet(android.bluetooth.le.AdvertisingSetCallback);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT}) public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT}) public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT}) public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT}) public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT}) public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, int, android.bluetooth.le.AdvertisingSetCallback);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT}) public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, int, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void stopAdvertising(android.bluetooth.le.AdvertiseCallback);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void stopAdvertisingSet(android.bluetooth.le.AdvertisingSetCallback);
   }
 
   public final class BluetoothLeScanner {
-    method public void flushPendingScanResults(android.bluetooth.le.ScanCallback);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public void startScan(android.bluetooth.le.ScanCallback);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public void startScan(java.util.List<android.bluetooth.le.ScanFilter>, android.bluetooth.le.ScanSettings, android.bluetooth.le.ScanCallback);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public int startScan(@Nullable java.util.List<android.bluetooth.le.ScanFilter>, @Nullable android.bluetooth.le.ScanSettings, @NonNull android.app.PendingIntent);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public void stopScan(android.bluetooth.le.ScanCallback);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public void stopScan(android.app.PendingIntent);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void flushPendingScanResults(android.bluetooth.le.ScanCallback);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void startScan(android.bluetooth.le.ScanCallback);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void startScan(java.util.List<android.bluetooth.le.ScanFilter>, android.bluetooth.le.ScanSettings, android.bluetooth.le.ScanCallback);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public int startScan(@Nullable java.util.List<android.bluetooth.le.ScanFilter>, @Nullable android.bluetooth.le.ScanSettings, @NonNull android.app.PendingIntent);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void stopScan(android.bluetooth.le.ScanCallback);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void stopScan(android.app.PendingIntent);
     field public static final String EXTRA_CALLBACK_TYPE = "android.bluetooth.le.extra.CALLBACK_TYPE";
     field public static final String EXTRA_ERROR_CODE = "android.bluetooth.le.extra.ERROR_CODE";
     field public static final String EXTRA_LIST_SCAN_RESULT = "android.bluetooth.le.extra.LIST_SCAN_RESULT";
@@ -18824,6 +18824,7 @@
 package android.hardware.display {
 
   public final class DeviceProductInfo implements android.os.Parcelable {
+    ctor public DeviceProductInfo(@Nullable String, @NonNull String, @NonNull String, @IntRange(from=1990) int, int);
     method public int describeContents();
     method public int getConnectionToSinkType();
     method @IntRange(from=0xffffffff, to=53) public int getManufactureWeek();
@@ -20379,7 +20380,7 @@
     method @NonNull public java.util.List<android.media.AudioDeviceInfo> getAvailableCommunicationDevices();
     method @Nullable public android.media.AudioDeviceInfo getCommunicationDevice();
     method public android.media.AudioDeviceInfo[] getDevices(int);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public int getEncodedSurroundMode();
+    method public int getEncodedSurroundMode();
     method public java.util.List<android.media.MicrophoneInfo> getMicrophones() throws java.io.IOException;
     method public int getMode();
     method public String getParameters(String);
@@ -20402,7 +20403,7 @@
     method public static boolean isOffloadedPlaybackSupported(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes);
     method public boolean isSpeakerphoneOn();
     method public boolean isStreamMute(int);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public boolean isSurroundFormatEnabled(int);
+    method public boolean isSurroundFormatEnabled(int);
     method public boolean isVolumeFixed();
     method @Deprecated public boolean isWiredHeadsetOn();
     method public void loadSoundEffects();
@@ -25217,7 +25218,6 @@
     method public float getPlaybackSpeed();
     method public long getPosition();
     method public int getState();
-    method public boolean isActive();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final long ACTION_FAST_FORWARD = 64L; // 0x40L
     field public static final long ACTION_PAUSE = 2L; // 0x2L
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 2497827..b653410 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -161,6 +161,10 @@
     method public void onVolumeChanged(@NonNull android.media.session.MediaSession.Token, int);
   }
 
+  public final class PlaybackState implements android.os.Parcelable {
+    method public boolean isActiveState();
+  }
+
 }
 
 package android.net {
@@ -232,7 +236,7 @@
   }
 
   public class VpnManager {
-    field @Deprecated public static final int TYPE_VPN_LEGACY = 3; // 0x3
+    field public static final int TYPE_VPN_LEGACY = 3; // 0x3
     field public static final int TYPE_VPN_NONE = -1; // 0xffffffff
     field public static final int TYPE_VPN_OEM = 4; // 0x4
     field public static final int TYPE_VPN_PLATFORM = 2; // 0x2
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 199d77a..f7b4cdc 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1901,7 +1901,7 @@
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getDynamicBufferSupport();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setBufferLengthMillis(int, int);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
     field public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD = 1; // 0x1
     field public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING = 2; // 0x2
     field public static final int DYNAMIC_BUFFER_SUPPORT_NONE = 0; // 0x0
@@ -1917,22 +1917,22 @@
     method public void finalize();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isAudioPlaying(@NonNull android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
     field @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
   }
 
   public final class BluetoothAdapter {
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean addOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener);
-    method public boolean disableBLE();
-    method public boolean enableBLE();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean enableNoAutoConnect();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disableBLE();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean enableBLE();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean enableNoAutoConnect();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void generateLocalOobData(int, @NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.OobDataCallback);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public long getDiscoveryEndMillis();
     method public boolean isBleScanAlwaysAvailable();
     method public boolean isLeEnabled();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean removeActiveDevice(int);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean removeActiveDevice(int);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean removeOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setActiveDevice(@NonNull android.bluetooth.BluetoothDevice, int);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean setActiveDevice(@NonNull android.bluetooth.BluetoothDevice, int);
     field public static final String ACTION_BLE_STATE_CHANGED = "android.bluetooth.adapter.action.BLE_STATE_CHANGED";
     field public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE";
     field public static final int ACTIVE_DEVICE_ALL = 2; // 0x2
@@ -1954,14 +1954,14 @@
 
   public final class BluetoothDevice implements android.os.Parcelable {
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean canBondWithoutDialog();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean cancelBondProcess();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean createBondOutOfBand(int, @Nullable android.bluetooth.OobData, @Nullable android.bluetooth.OobData);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean cancelBondProcess();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean createBondOutOfBand(int, @Nullable android.bluetooth.OobData, @Nullable android.bluetooth.OobData);
     method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public byte[] getMetadata(int);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getSimAccessPermission();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEncrypted();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getSimAccessPermission();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isConnected();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isEncrypted();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isInSilenceMode();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean removeBond();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean removeBond();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMessageAccessPermission(int);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMetadata(int, @NonNull byte[]);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPhonebookAccessPermission(int);
@@ -2002,51 +2002,51 @@
   }
 
   public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean connect(android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean disconnect(android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean connect(android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnect(android.bluetooth.BluetoothDevice);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
   }
 
   public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile {
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public long getHiSyncId(@NonNull android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
   }
 
   public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile {
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
   }
 
   public final class BluetoothHidHost implements android.bluetooth.BluetoothProfile {
-    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
-    field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
   }
 
   public final class BluetoothMap implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void close();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) protected void finalize();
-    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+    method public void close();
+    method protected void finalize();
+    method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
     field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED";
   }
 
   public final class BluetoothMapClient implements android.bluetooth.BluetoothProfile {
-    method @RequiresPermission(android.Manifest.permission.SEND_SMS) public boolean sendMessage(@NonNull android.bluetooth.BluetoothDevice, @NonNull java.util.Collection<android.net.Uri>, @NonNull String, @Nullable android.app.PendingIntent, @Nullable android.app.PendingIntent);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.SEND_SMS}) public boolean sendMessage(@NonNull android.bluetooth.BluetoothDevice, @NonNull java.util.Collection<android.net.Uri>, @NonNull String, @Nullable android.app.PendingIntent, @Nullable android.app.PendingIntent);
   }
 
   public final class BluetoothPan implements android.bluetooth.BluetoothProfile {
-    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isTetheringOn();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setBluetoothTethering(boolean);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
-    field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED";
-    field public static final String ACTION_TETHERING_STATE_CHANGED = "android.bluetooth.action.TETHERING_STATE_CHANGED";
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isTetheringOn();
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.TETHER_PRIVILEGED}) public void setBluetoothTethering(boolean);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_TETHERING_STATE_CHANGED = "android.bluetooth.action.TETHERING_STATE_CHANGED";
     field public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE";
     field public static final String EXTRA_TETHERING_STATE = "android.bluetooth.extra.TETHERING_STATE";
     field public static final int LOCAL_NAP_ROLE = 1; // 0x1
@@ -2060,7 +2060,7 @@
 
   public class BluetoothPbap implements android.bluetooth.BluetoothProfile {
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
     field @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
   }
 
@@ -2185,9 +2185,9 @@
 package android.bluetooth.le {
 
   public final class BluetoothLeScanner {
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADMIN, android.Manifest.permission.UPDATE_DEVICE_STATS}) public void startScanFromSource(android.os.WorkSource, android.bluetooth.le.ScanCallback);
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADMIN, android.Manifest.permission.UPDATE_DEVICE_STATS}) public void startScanFromSource(java.util.List<android.bluetooth.le.ScanFilter>, android.bluetooth.le.ScanSettings, android.os.WorkSource, android.bluetooth.le.ScanCallback);
-    method public void startTruncatedScan(java.util.List<android.bluetooth.le.TruncatedFilter>, android.bluetooth.le.ScanSettings, android.bluetooth.le.ScanCallback);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.UPDATE_DEVICE_STATS}) public void startScanFromSource(android.os.WorkSource, android.bluetooth.le.ScanCallback);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.UPDATE_DEVICE_STATS}) public void startScanFromSource(java.util.List<android.bluetooth.le.ScanFilter>, android.bluetooth.le.ScanSettings, android.os.WorkSource, android.bluetooth.le.ScanCallback);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void startTruncatedScan(java.util.List<android.bluetooth.le.TruncatedFilter>, android.bluetooth.le.ScanSettings, android.bluetooth.le.ScanCallback);
   }
 
   public final class ResultStorageDescriptor implements android.os.Parcelable {
@@ -7944,16 +7944,16 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported();
-    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnStateCallback);
+    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
     method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int);
-    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnStateCallback(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnStateCallback);
+    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
     field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1
   }
 
-  public static interface NfcAdapter.ControllerAlwaysOnStateCallback {
-    method public void onStateChanged(boolean);
+  public static interface NfcAdapter.ControllerAlwaysOnListener {
+    method public void onControllerAlwaysOnChanged(boolean);
   }
 
   public static interface NfcAdapter.NfcUnlockHandler {
diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt
index b50b8dd..bf9f4f1 100644
--- a/core/api/system-removed.txt
+++ b/core/api/system-removed.txt
@@ -51,7 +51,7 @@
 package android.bluetooth {
 
   public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
-    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setPriority(android.bluetooth.BluetoothDevice, int);
+    method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean setPriority(android.bluetooth.BluetoothDevice, int);
   }
 
 }
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 20e9187..0e1b24a 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -161,6 +161,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void moveTaskToRootTask(int, int, boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void removeRootTasksInWindowingModes(@NonNull int[]);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void removeRootTasksWithActivityTypes(@NonNull int[]);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean removeTask(int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void resizeTask(int, android.graphics.Rect);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void startSystemLockTaskMode(int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void stopSystemLockTaskMode();
@@ -2009,6 +2010,7 @@
 
   public final class PermissionManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData();
+    method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData(boolean);
     method @NonNull public android.content.AttributionSource registerAttributionSource(@NonNull android.content.AttributionSource);
   }
 
@@ -3172,5 +3174,12 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void applyTransaction(@NonNull android.window.WindowContainerTransaction);
   }
 
+  @UiContext public abstract class WindowProviderService extends android.app.Service {
+    ctor public WindowProviderService();
+    method public final void attachToWindowToken(@NonNull android.os.IBinder);
+    method @Nullable public android.os.Bundle getWindowContextOptions();
+    method public abstract int getWindowType();
+  }
+
 }
 
diff --git a/core/java/android/annotation/RequiresPermission.java b/core/java/android/annotation/RequiresPermission.java
index 1d89e31..303ab41 100644
--- a/core/java/android/annotation/RequiresPermission.java
+++ b/core/java/android/annotation/RequiresPermission.java
@@ -20,7 +20,7 @@
 import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.ElementType.METHOD;
 import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static java.lang.annotation.RetentionPolicy.CLASS;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
@@ -76,7 +76,7 @@
  *
  * @hide
  */
-@Retention(SOURCE)
+@Retention(CLASS)
 @Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER})
 public @interface RequiresPermission {
     /**
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 627017c..28d6fbb 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -469,7 +469,8 @@
         }
     }
 
-    /** @hide */
+    /** Removes task by a given taskId */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
     public boolean removeTask(int taskId) {
         try {
             return getService().removeTask(taskId);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 8ff14b0..98fee9c 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4390,11 +4390,12 @@
         try {
             if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
 
-            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
             Application app = packageInfo.makeApplication(false, mInstrumentation);
             java.lang.ClassLoader cl = packageInfo.getClassLoader();
             service = packageInfo.getAppFactory()
                     .instantiateService(cl, data.info.name, data.intent);
+            final ContextImpl context = ContextImpl.getImpl(service
+                    .createServiceBaseContext(this, packageInfo));
             // Service resources must be initialized with the same loaders as the application
             // context.
             context.getResources().addLoaders(
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index dfc105a..8574678 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -614,7 +614,7 @@
      * tombstone traces will be returned for
      * {@link #REASON_CRASH_NATIVE}, with an InputStream containing a protobuf with
      * <a href="https://android.googlesource.com/platform/system/core/+/refs/heads/master/debuggerd/proto/tombstone.proto">this schema</a>.
-     * Note thatbecause these traces are kept in a separate global circular buffer, crashes may be
+     * Note that because these traces are kept in a separate global circular buffer, crashes may be
      * overwritten by newer crashes (including from other applications), so this may still return
      * null.
      *
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 4cf3a80..ca08683 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -53,7 +53,6 @@
 import android.os.UserHandle;
 import android.util.AndroidException;
 import android.util.ArraySet;
-import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.os.IResultReceiver;
@@ -371,19 +370,9 @@
                 "Cannot set both FLAG_IMMUTABLE and FLAG_MUTABLE for PendingIntent");
         }
 
-        // TODO(b/178092897) Remove the below instrumentation check and enforce
-        // the explicit mutability requirement for apps under instrumentation.
-        ActivityThread thread = ActivityThread.currentActivityThread();
-        Instrumentation mInstrumentation = thread.getInstrumentation();
-
         if (Compatibility.isChangeEnabled(PENDING_INTENT_EXPLICIT_MUTABILITY_REQUIRED)
                 && !flagImmutableSet && !flagMutableSet) {
-
-            if (mInstrumentation.isInstrumenting()) {
-                Log.e(TAG, msg);
-            } else {
                 throw new IllegalArgumentException(msg);
-            }
         }
     }
 
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 2ceea7f..0ab3f2f 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -861,6 +861,19 @@
     }
 
     /**
+     * Creates the base {@link Context} of this {@link Service}.
+     * Users may override this API to create customized base context.
+     *
+     * @see android.window.WindowProviderService WindowProviderService class for example
+     * @see ContextWrapper#attachBaseContext(Context)
+     *
+     * @hide
+     */
+    public Context createServiceBaseContext(ActivityThread mainThread, LoadedApk packageInfo) {
+        return ContextImpl.createAppContext(mainThread, packageInfo);
+    }
+
+    /**
      * @hide
      * Clean up any references to avoid leaks.
      */
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index e93138b..759597c 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -28,6 +28,7 @@
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
 import static com.android.internal.widget.LockPatternUtils.MIN_LOCK_PASSWORD_SIZE;
 import static com.android.internal.widget.PasswordValidationError.CONTAINS_INVALID_CHARACTERS;
 import static com.android.internal.widget.PasswordValidationError.CONTAINS_SEQUENCE;
@@ -74,11 +75,8 @@
     // consider it a complex PIN/password.
     public static final int MAX_ALLOWED_SEQUENCE = 3;
 
-    // One of CREDENTIAL_TYPE_NONE, CREDENTIAL_TYPE_PATTERN or CREDENTIAL_TYPE_PASSWORD.
-    // Note that this class still uses CREDENTIAL_TYPE_PASSWORD to represent both numeric PIN
-    // and alphabetic password. This is OK as long as this definition is only used internally,
-    // and the value never gets mixed up with credential types from other parts of the framework.
-    // TODO: fix this (ideally after we move logic to PasswordPolicy)
+    // One of CREDENTIAL_TYPE_NONE, CREDENTIAL_TYPE_PATTERN, CREDENTIAL_TYPE_PIN or
+    // CREDENTIAL_TYPE_PASSWORD.
     public @CredentialType int credType;
     // Fields below only make sense when credType is PASSWORD.
     public int length = 0;
@@ -192,13 +190,15 @@
     /**
      * Returns the {@code PasswordMetrics} for a given credential.
      *
-     * If the credential is a pin or a password, equivalent to {@link #computeForPassword(byte[])}.
-     * {@code credential} cannot be null when {@code type} is
+     * If the credential is a pin or a password, equivalent to
+     * {@link #computeForPasswordOrPin(byte[], boolean)}. {@code credential} cannot be null
+     * when {@code type} is
      * {@link com.android.internal.widget.LockPatternUtils#CREDENTIAL_TYPE_PASSWORD}.
      */
     public static PasswordMetrics computeForCredential(LockscreenCredential credential) {
         if (credential.isPassword() || credential.isPin()) {
-            return PasswordMetrics.computeForPassword(credential.getCredential());
+            return PasswordMetrics.computeForPasswordOrPin(credential.getCredential(),
+                    credential.isPin());
         } else if (credential.isPattern())  {
             return new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
         } else if (credential.isNone()) {
@@ -209,9 +209,9 @@
     }
 
     /**
-     * Returns the {@code PasswordMetrics} for a given password
+     * Returns the {@code PasswordMetrics} for a given password or pin
      */
-    public static PasswordMetrics computeForPassword(@NonNull byte[] password) {
+    public static PasswordMetrics computeForPasswordOrPin(byte[] password, boolean isPin) {
         // Analyse the characters used
         int letters = 0;
         int upperCase = 0;
@@ -245,8 +245,9 @@
             }
         }
 
+        final int credType = isPin ? CREDENTIAL_TYPE_PIN : CREDENTIAL_TYPE_PASSWORD;
         final int seqLength = maxLengthSequence(password);
-        return new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD, length, letters, upperCase, lowerCase,
+        return new PasswordMetrics(credType, length, letters, upperCase, lowerCase,
                 numeric, symbols, nonLetter, nonNumeric, seqLength);
     }
 
@@ -353,7 +354,7 @@
      */
     public void maxWith(PasswordMetrics other) {
         credType = Math.max(credType, other.credType);
-        if (credType != CREDENTIAL_TYPE_PASSWORD) {
+        if (credType != CREDENTIAL_TYPE_PASSWORD && credType != CREDENTIAL_TYPE_PIN) {
             return;
         }
         length = Math.max(length, other.length);
@@ -408,7 +409,7 @@
 
             @Override
             boolean allowsCredType(int credType) {
-                return credType == CREDENTIAL_TYPE_PASSWORD;
+                return credType == CREDENTIAL_TYPE_PASSWORD || credType == CREDENTIAL_TYPE_PIN;
             }
         },
         BUCKET_MEDIUM(PASSWORD_COMPLEXITY_MEDIUM) {
@@ -424,7 +425,7 @@
 
             @Override
             boolean allowsCredType(int credType) {
-                return credType == CREDENTIAL_TYPE_PASSWORD;
+                return credType == CREDENTIAL_TYPE_PASSWORD || credType == CREDENTIAL_TYPE_PIN;
             }
         },
         BUCKET_LOW(PASSWORD_COMPLEXITY_LOW) {
@@ -489,7 +490,7 @@
         if (!bucket.allowsCredType(credType)) {
             return false;
         }
-        if (credType != CREDENTIAL_TYPE_PASSWORD) {
+        if (credType != CREDENTIAL_TYPE_PASSWORD && credType != CREDENTIAL_TYPE_PIN) {
             return true;
         }
         return (bucket.canHaveSequence() || seqLength <= MAX_ALLOWED_SEQUENCE)
@@ -529,7 +530,7 @@
                     new PasswordValidationError(CONTAINS_INVALID_CHARACTERS, 0));
         }
 
-        final PasswordMetrics enteredMetrics = computeForPassword(password);
+        final PasswordMetrics enteredMetrics = computeForPasswordOrPin(password, isPin);
         return validatePasswordMetrics(adminMetrics, minComplexity, isPin, enteredMetrics);
     }
 
@@ -555,8 +556,8 @@
                 || !bucket.allowsCredType(actualMetrics.credType)) {
             return Collections.singletonList(new PasswordValidationError(WEAK_CREDENTIAL_TYPE, 0));
         }
-        // TODO: this needs to be modified if CREDENTIAL_TYPE_PIN is added.
-        if (actualMetrics.credType != CREDENTIAL_TYPE_PASSWORD) {
+        if (actualMetrics.credType != CREDENTIAL_TYPE_PASSWORD
+                && actualMetrics.credType != CREDENTIAL_TYPE_PIN) {
             return Collections.emptyList(); // Nothing to check for pattern or none.
         }
 
diff --git a/core/java/android/app/admin/PasswordPolicy.java b/core/java/android/app/admin/PasswordPolicy.java
index 13f11ad..0544a36 100644
--- a/core/java/android/app/admin/PasswordPolicy.java
+++ b/core/java/android/app/admin/PasswordPolicy.java
@@ -20,6 +20,7 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
@@ -27,6 +28,7 @@
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
 
 /**
  * {@hide}
@@ -58,14 +60,20 @@
         } else if (quality == PASSWORD_QUALITY_BIOMETRIC_WEAK
                 || quality == PASSWORD_QUALITY_SOMETHING) {
             return new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
-        } // quality is NUMERIC or stronger.
+        } else if (quality == PASSWORD_QUALITY_NUMERIC
+                || quality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
+            PasswordMetrics result = new PasswordMetrics(CREDENTIAL_TYPE_PIN);
+            result.length = length;
+            if (quality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
+                result.seqLength = PasswordMetrics.MAX_ALLOWED_SEQUENCE;
+            }
+            return result;
+        } // quality is ALPHABETIC or stronger.
 
         PasswordMetrics result = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
         result.length = length;
 
-        if (quality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
-            result.seqLength = PasswordMetrics.MAX_ALLOWED_SEQUENCE;
-        } else if (quality == PASSWORD_QUALITY_ALPHABETIC) {
+        if (quality == PASSWORD_QUALITY_ALPHABETIC) {
             result.nonNumeric = 1;
         } else if (quality == PASSWORD_QUALITY_ALPHANUMERIC) {
             result.numeric = 1;
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 16413e1..a268e16 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -23,6 +23,9 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -69,10 +72,10 @@
      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_CONNECTION_STATE_CHANGED =
             "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED";
@@ -90,10 +93,10 @@
      *
      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
      * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_PLAYING_STATE_CHANGED =
             "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED";
@@ -112,11 +115,11 @@
      * be null if no device is active. </li>
      * </ul>
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
-     *
      * @hide
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     @UnsupportedAppUsage(trackingBug = 171933273)
     public static final String ACTION_ACTIVE_DEVICE_CHANGED =
@@ -133,11 +136,11 @@
      * connected, otherwise it is not included.</li>
      * </ul>
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
-     *
      * @hide
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     @UnsupportedAppUsage(trackingBug = 181103983)
     public static final String ACTION_CODEC_CONFIG_CHANGED =
@@ -307,7 +310,9 @@
      * @return false on immediate error, true otherwise
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @UnsupportedAppUsage
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
@@ -347,7 +352,9 @@
      * @return false on immediate error, true otherwise
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @UnsupportedAppUsage
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
@@ -368,6 +375,8 @@
      * {@inheritDoc}
      */
     @Override
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
         try {
@@ -387,6 +396,8 @@
      * {@inheritDoc}
      */
     @Override
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
         try {
@@ -406,6 +417,8 @@
      * {@inheritDoc}
      */
     @Override
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @BtProfileState int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
         try {
@@ -441,7 +454,9 @@
      * @return false on immediate error, true otherwise
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @UnsupportedAppUsage(trackingBug = 171933273)
     public boolean setActiveDevice(@Nullable BluetoothDevice device) {
         if (DBG) log("setActiveDevice(" + device + ")");
@@ -468,7 +483,9 @@
      */
     @UnsupportedAppUsage(trackingBug = 171933273)
     @Nullable
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothDevice getActiveDevice() {
         if (VDBG) log("getActiveDevice()");
         try {
@@ -495,7 +512,10 @@
      * @return true if priority is set, false on error
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
         return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
@@ -514,7 +534,10 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -546,7 +569,9 @@
      * @return priority of the device
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
@@ -620,6 +645,8 @@
      * @param volume Absolute volume to be set on AVRCP side
      * @hide
      */
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void setAvrcpAbsoluteVolume(int volume) {
         if (DBG) Log.d(TAG, "setAvrcpAbsoluteVolume");
         try {
@@ -636,10 +663,11 @@
     /**
      * Check if A2DP profile is streaming music.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param device BluetoothDevice device
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean isA2dpPlaying(BluetoothDevice device) {
         try {
             final IBluetoothA2dp service = getService();
@@ -662,6 +690,7 @@
      *
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean shouldSendVolumeKeys(BluetoothDevice device) {
         if (isEnabled() && isValidDevice(device)) {
             ParcelUuid[] uuids = device.getUuids();
@@ -686,7 +715,9 @@
      */
     @UnsupportedAppUsage(trackingBug = 181103983)
     @Nullable
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) {
         if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")");
         verifyDeviceNotNull(device, "getCodecStatus");
@@ -714,7 +745,9 @@
      * @hide
      */
     @UnsupportedAppUsage(trackingBug = 181103983)
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void setCodecConfigPreference(@NonNull BluetoothDevice device,
                                          @NonNull BluetoothCodecConfig codecConfig) {
         if (DBG) Log.d(TAG, "setCodecConfigPreference(" + device + ")");
@@ -744,7 +777,9 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void enableOptionalCodecs(@NonNull BluetoothDevice device) {
         if (DBG) Log.d(TAG, "enableOptionalCodecs(" + device + ")");
         verifyDeviceNotNull(device, "enableOptionalCodecs");
@@ -759,7 +794,9 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void disableOptionalCodecs(@NonNull BluetoothDevice device) {
         if (DBG) Log.d(TAG, "disableOptionalCodecs(" + device + ")");
         verifyDeviceNotNull(device, "disableOptionalCodecs");
@@ -773,6 +810,7 @@
      * active A2DP Bluetooth device.
      * @param enable if true, enable the optional codecs, other disable them
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     private void enableDisableOptionalCodecs(BluetoothDevice device, boolean enable) {
         try {
             final IBluetoothA2dp service = getService();
@@ -800,7 +838,9 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @OptionalCodecsSupportStatus
     public int isOptionalCodecsSupported(@NonNull BluetoothDevice device) {
         verifyDeviceNotNull(device, "isOptionalCodecsSupported");
@@ -826,7 +866,9 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @OptionalCodecsPreferenceStatus
     public int isOptionalCodecsEnabled(@NonNull BluetoothDevice device) {
         verifyDeviceNotNull(device, "isOptionalCodecsEnabled");
@@ -853,7 +895,9 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void setOptionalCodecsEnabled(@NonNull BluetoothDevice device,
             @OptionalCodecsPreferenceStatus int value) {
         verifyDeviceNotNull(device, "setOptionalCodecsEnabled");
diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java
index 67f3d7b..d81316e 100755
--- a/core/java/android/bluetooth/BluetoothA2dpSink.java
+++ b/core/java/android/bluetooth/BluetoothA2dpSink.java
@@ -21,6 +21,9 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.Binder;
@@ -160,7 +163,9 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothA2dpSink service = getService();
@@ -243,8 +248,6 @@
      * Get the current audio configuration for the A2DP source device,
      * or null if the device has no audio configuration
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param device Remote bluetooth device.
      * @return audio configuration for the device, or null
      *
@@ -252,6 +255,7 @@
      *
      * @hide
      */
+    @RequiresLegacyBluetoothPermission
     public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
         if (VDBG) log("getAudioConfig(" + device + ")");
         final IBluetoothA2dpSink service = getService();
@@ -278,7 +282,10 @@
      * @return true if priority is set, false on error
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
         return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
@@ -297,7 +304,10 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED
+    })
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 236185e..972e9e6 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -17,7 +17,6 @@
 
 package android.bluetooth;
 
-import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -25,11 +24,18 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.app.ActivityThread;
 import android.app.PropertyInvalidatedCache;
 import android.bluetooth.BluetoothDevice.Transport;
 import android.bluetooth.BluetoothProfile.ConnectionPolicy;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
+import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
+import android.bluetooth.annotations.RequiresBluetoothScanPermission;
 import android.bluetooth.le.BluetoothLeAdvertiser;
 import android.bluetooth.le.BluetoothLeScanner;
 import android.bluetooth.le.PeriodicAdvertisingManager;
@@ -98,11 +104,6 @@
  * Bluetooth LE devices with {@link #startLeScan(LeScanCallback callback)}.
  * </p>
  * <p>This class is thread safe.</p>
- * <p class="note"><strong>Note:</strong>
- * Most methods require the {@link android.Manifest.permission#BLUETOOTH}
- * permission and some also require the
- * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
- * </p>
  * <div class="special reference">
  * <h3>Developer Guides</h3>
  * <p>
@@ -144,8 +145,8 @@
      * <p>Always contains the extra fields {@link #EXTRA_STATE} and {@link
      * #EXTRA_PREVIOUS_STATE} containing the new and old states
      * respectively.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
      */
+    @RequiresLegacyBluetoothPermission
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String
             ACTION_STATE_CHANGED = "android.bluetooth.adapter.action.STATE_CHANGED";
 
@@ -278,8 +279,10 @@
      * <p>Applications can also listen for {@link #ACTION_SCAN_MODE_CHANGED}
      * for global notification whenever the scan mode changes. For example, an
      * application can be notified when the device has ended discoverability.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothAdvertisePermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String
             ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";
 
@@ -305,8 +308,10 @@
      * has rejected the request or an error has occurred.
      * <p>Applications can also listen for {@link #ACTION_STATE_CHANGED}
      * for global notification whenever Bluetooth is turned on or off.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String
             ACTION_REQUEST_ENABLE = "android.bluetooth.adapter.action.REQUEST_ENABLE";
 
@@ -325,10 +330,12 @@
      * has rejected the request or an error has occurred.
      * <p>Applications can also listen for {@link #ACTION_STATE_CHANGED}
      * for global notification whenever Bluetooth is turned on or off.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
      *
      * @hide
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String
             ACTION_REQUEST_DISABLE = "android.bluetooth.adapter.action.REQUEST_DISABLE";
 
@@ -355,8 +362,10 @@
      * <p>Always contains the extra fields {@link #EXTRA_SCAN_MODE} and {@link
      * #EXTRA_PREVIOUS_SCAN_MODE} containing the new and old scan modes
      * respectively.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothScanPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String
             ACTION_SCAN_MODE_CHANGED = "android.bluetooth.adapter.action.SCAN_MODE_CHANGED";
 
@@ -508,15 +517,19 @@
      * progress, and existing connections will experience limited bandwidth
      * and high latency. Use {@link #cancelDiscovery()} to cancel an ongoing
      * discovery.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothScanPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String
             ACTION_DISCOVERY_STARTED = "android.bluetooth.adapter.action.DISCOVERY_STARTED";
     /**
      * Broadcast Action: The local Bluetooth adapter has finished the device
      * discovery process.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothScanPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String
             ACTION_DISCOVERY_FINISHED = "android.bluetooth.adapter.action.DISCOVERY_FINISHED";
 
@@ -526,8 +539,10 @@
      * <p>This name is visible to remote Bluetooth devices.
      * <p>Always contains the extra field {@link #EXTRA_LOCAL_NAME} containing
      * the name.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String
             ACTION_LOCAL_NAME_CHANGED = "android.bluetooth.adapter.action.LOCAL_NAME_CHANGED";
     /**
@@ -559,9 +574,10 @@
      * {@link #EXTRA_CONNECTION_STATE} or {@link #EXTRA_PREVIOUS_CONNECTION_STATE}
      * can be any of {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String
             ACTION_CONNECTION_STATE_CHANGED =
             "android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED";
@@ -870,7 +886,7 @@
      *
      * @return true if the local adapter is turned on
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
     public boolean isEnabled() {
         return getState() == BluetoothAdapter.STATE_ON;
     }
@@ -921,6 +937,7 @@
      * @hide
      */
     @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disableBLE() {
         if (!isBleScanAlwaysAvailable()) {
             return false;
@@ -966,6 +983,7 @@
      * @hide
      */
     @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean enableBLE() {
         if (!isBleScanAlwaysAvailable()) {
             return false;
@@ -986,6 +1004,7 @@
             new PropertyInvalidatedCache<Void, Integer>(
                 8, BLUETOOTH_GET_STATE_CACHE_PROPERTY) {
                 @Override
+                @SuppressLint("AndroidFrameworkRequiresPermission")
                 protected Integer recompute(Void query) {
                     try {
                         return mService.getState();
@@ -1039,7 +1058,7 @@
      *
      * @return current state of Bluetooth adapter
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
     @AdapterState
     public int getState() {
         int state = getStateInternal();
@@ -1075,7 +1094,7 @@
      * @return current state of Bluetooth adapter
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
     @AdapterState
     @UnsupportedAppUsage(publicAlternatives = "Use {@link #getState()} instead to determine "
             + "whether you can use BLE & BT classic.")
@@ -1122,7 +1141,9 @@
      *
      * @return true to indicate adapter startup has begun, or false on immediate error
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean enable() {
         if (isEnabled()) {
             if (DBG) {
@@ -1159,7 +1180,9 @@
      *
      * @return true to indicate adapter shutdown has begun, or false on immediate error
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disable() {
         try {
             return mManagerService.disable(ActivityThread.currentPackageName(), true);
@@ -1172,13 +1195,13 @@
     /**
      * Turn off the local Bluetooth adapter and don't persist the setting.
      *
-     * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_ADMIN}
-     * permission
-     *
      * @return true to indicate adapter shutdown has begun, or false on immediate error
      * @hide
      */
     @UnsupportedAppUsage(trackingBug = 171933273)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disable(boolean persist) {
 
         try {
@@ -1195,7 +1218,12 @@
      *
      * @return Bluetooth hardware address as string
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.LOCAL_MAC_ADDRESS,
+    })
     public String getAddress() {
         try {
             return mManagerService.getAddress();
@@ -1208,10 +1236,12 @@
     /**
      * Get the friendly Bluetooth name of the local Bluetooth adapter.
      * <p>This name is visible to remote Bluetooth devices.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
      *
      * @return the Bluetooth name, or null on error
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public String getName() {
         try {
             return mManagerService.getName();
@@ -1228,7 +1258,7 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean factoryReset() {
         try {
             mServiceLock.readLock().lock();
@@ -1253,7 +1283,9 @@
      * @hide
      */
     @UnsupportedAppUsage
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @Nullable ParcelUuid[] getUuids() {
         if (getState() != STATE_ON) {
             return null;
@@ -1285,7 +1317,9 @@
      * @param name a valid Bluetooth name
      * @return true if the name was set, false otherwise
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean setName(String name) {
         if (getState() != STATE_ON) {
             return false;
@@ -1311,7 +1345,9 @@
      *
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothClass getBluetoothClass() {
         if (getState() != STATE_ON) {
             return null;
@@ -1340,7 +1376,7 @@
      *
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setBluetoothClass(BluetoothClass bluetoothClass) {
         if (getState() != STATE_ON) {
             return false;
@@ -1367,7 +1403,9 @@
      *
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @IoCapability
     public int getIoCapability() {
         if (getState() != STATE_ON) return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
@@ -1395,7 +1433,7 @@
      *
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setIoCapability(@IoCapability int capability) {
         if (getState() != STATE_ON) return false;
         try {
@@ -1418,7 +1456,9 @@
      *
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @IoCapability
     public int getLeIoCapability() {
         if (getState() != STATE_ON) return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
@@ -1446,7 +1486,7 @@
      *
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setLeIoCapability(@IoCapability int capability) {
         if (getState() != STATE_ON) return false;
         try {
@@ -1475,7 +1515,9 @@
      *
      * @return scan mode
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothScanPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     @ScanMode
     public int getScanMode() {
         if (getState() != STATE_ON) {
@@ -1522,7 +1564,9 @@
      */
     @UnsupportedAppUsage(publicAlternatives = "Use {@link #ACTION_REQUEST_DISCOVERABLE}, which "
             + "shows UI that confirms the user wants to go into discoverable mode.")
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothScanPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     public boolean setScanMode(@ScanMode int mode, long durationMillis) {
         if (getState() != STATE_ON) {
             return false;
@@ -1571,7 +1615,9 @@
      * @hide
      */
     @UnsupportedAppUsage
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothScanPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     public boolean setScanMode(@ScanMode int mode) {
         if (getState() != STATE_ON) {
             return false;
@@ -1591,6 +1637,7 @@
 
     /** @hide */
     @UnsupportedAppUsage
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     public int getDiscoverableTimeout() {
         if (getState() != STATE_ON) {
             return -1;
@@ -1610,6 +1657,7 @@
 
     /** @hide */
     @UnsupportedAppUsage
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     public void setDiscoverableTimeout(int timeout) {
         if (getState() != STATE_ON) {
             return;
@@ -1635,7 +1683,7 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public long getDiscoveryEndMillis() {
         try {
             mServiceLock.readLock().lock();
@@ -1703,7 +1751,10 @@
      *
      * @return true on success, false on error
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothScanPermission
+    @RequiresBluetoothLocationPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     public boolean startDiscovery() {
         if (getState() != STATE_ON) {
             return false;
@@ -1737,7 +1788,9 @@
      *
      * @return true on success, false on error
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothScanPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     public boolean cancelDiscovery() {
         if (getState() != STATE_ON) {
             return false;
@@ -1773,7 +1826,9 @@
      *
      * @return true if discovering
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothScanPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     public boolean isDiscovering() {
         if (getState() != STATE_ON) {
             return false;
@@ -1805,7 +1860,11 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+    })
     public boolean removeActiveDevice(@ActiveDeviceUse int profiles) {
         if (profiles != ACTIVE_DEVICE_AUDIO && profiles != ACTIVE_DEVICE_PHONE_CALL
                 && profiles != ACTIVE_DEVICE_ALL) {
@@ -1845,7 +1904,11 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+    })
     public boolean setActiveDevice(@NonNull BluetoothDevice device,
             @ActiveDeviceUse int profiles) {
         if (device == null) {
@@ -1889,7 +1952,11 @@
      *
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+    })
     public boolean connectAllEnabledProfiles(@NonNull BluetoothDevice device) {
         try {
             mServiceLock.readLock().lock();
@@ -1917,7 +1984,10 @@
      *
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean disconnectAllEnabledProfiles(@NonNull BluetoothDevice device) {
         try {
             mServiceLock.readLock().lock();
@@ -1938,6 +2008,7 @@
      *
      * @return true if Multiple Advertisement feature is supported
      */
+    @RequiresLegacyBluetoothPermission
     public boolean isMultipleAdvertisementSupported() {
         if (getState() != STATE_ON) {
             return false;
@@ -1981,6 +2052,7 @@
             new PropertyInvalidatedCache<Void, Boolean>(
                 8, BLUETOOTH_FILTERING_CACHE_PROPERTY) {
                 @Override
+                @SuppressLint("AndroidFrameworkRequiresPermission")
                 protected Boolean recompute(Void query) {
                     try {
                         mServiceLock.readLock().lock();
@@ -2012,6 +2084,7 @@
      *
      * @return true if chipset supports on-chip filtering
      */
+    @RequiresLegacyBluetoothPermission
     public boolean isOffloadedFilteringSupported() {
         if (!getLeAccess()) {
             return false;
@@ -2024,6 +2097,7 @@
      *
      * @return true if chipset supports on-chip scan batching
      */
+    @RequiresLegacyBluetoothPermission
     public boolean isOffloadedScanBatchingSupported() {
         if (!getLeAccess()) {
             return false;
@@ -2046,6 +2120,7 @@
      *
      * @return true if chipset supports LE 2M PHY feature
      */
+    @RequiresLegacyBluetoothPermission
     public boolean isLe2MPhySupported() {
         if (!getLeAccess()) {
             return false;
@@ -2068,6 +2143,7 @@
      *
      * @return true if chipset supports LE Coded PHY feature
      */
+    @RequiresLegacyBluetoothPermission
     public boolean isLeCodedPhySupported() {
         if (!getLeAccess()) {
             return false;
@@ -2090,6 +2166,7 @@
      *
      * @return true if chipset supports LE Extended Advertising feature
      */
+    @RequiresLegacyBluetoothPermission
     public boolean isLeExtendedAdvertisingSupported() {
         if (!getLeAccess()) {
             return false;
@@ -2112,6 +2189,7 @@
      *
      * @return true if chipset supports LE Periodic Advertising feature
      */
+    @RequiresLegacyBluetoothPermission
     public boolean isLePeriodicAdvertisingSupported() {
         if (!getLeAccess()) {
             return false;
@@ -2135,6 +2213,7 @@
      *
      * @return the maximum LE advertising data length.
      */
+    @RequiresLegacyBluetoothPermission
     public int getLeMaximumAdvertisingDataLength() {
         if (!getLeAccess()) {
             return 0;
@@ -2172,7 +2251,9 @@
      * @return the maximum number of connected audio devices
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getMaxConnectedAudioDevices() {
         try {
             mServiceLock.readLock().lock();
@@ -2193,6 +2274,7 @@
      * @return true if there are hw entries available for matching beacons
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean isHardwareTrackingFiltersAvailable() {
         if (!getLeAccess()) {
             return false;
@@ -2223,6 +2305,7 @@
      * instead.
      */
     @Deprecated
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public BluetoothActivityEnergyInfo getControllerActivityEnergyInfo(int updateType) {
         SynchronousResultReceiver receiver = new SynchronousResultReceiver();
         requestControllerActivityEnergyInfo(receiver);
@@ -2248,6 +2331,7 @@
      * @param result The callback to which to send the activity info.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public void requestControllerActivityEnergyInfo(ResultReceiver result) {
         try {
             mServiceLock.readLock().lock();
@@ -2275,7 +2359,9 @@
      *
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @NonNull List<BluetoothDevice> getMostRecentlyConnectedDevices() {
         if (getState() != STATE_ON) {
             return new ArrayList<>();
@@ -2303,7 +2389,9 @@
      *
      * @return unmodifiable set of {@link BluetoothDevice}, or null on error
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public Set<BluetoothDevice> getBondedDevices() {
         if (getState() != STATE_ON) {
             return toDeviceSet(new BluetoothDevice[0]);
@@ -2368,6 +2456,7 @@
                  * This method must not be called when mService is null.
                  */
                 @Override
+                @SuppressLint("AndroidFrameworkRequiresPermission")
                 protected Integer recompute(Void query) {
                     try {
                         return mService.getAdapterConnectionState();
@@ -2401,6 +2490,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresLegacyBluetoothPermission
     public int getConnectionState() {
         if (getState() != STATE_ON) {
             return BluetoothAdapter.STATE_DISCONNECTED;
@@ -2429,6 +2519,7 @@
             new PropertyInvalidatedCache<Integer, Integer>(
                 8, BLUETOOTH_PROFILE_CACHE_PROPERTY) {
                 @Override
+                @SuppressLint("AndroidFrameworkRequiresPermission")
                 protected Integer recompute(Integer query) {
                     try {
                         mServiceLock.readLock().lock();
@@ -2471,7 +2562,10 @@
      * {@link BluetoothProfile#STATE_CONNECTED},
      * {@link BluetoothProfile#STATE_DISCONNECTING}
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public int getProfileConnectionState(int profile) {
         if (getState() != STATE_ON) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -2486,7 +2580,6 @@
      * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming
      * connections from a listening {@link BluetoothServerSocket}.
      * <p>Valid RFCOMM channels are in range 1 to 30.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
      *
      * @param channel RFCOMM channel to listen on
      * @return a listening RFCOMM BluetoothServerSocket
@@ -2494,6 +2587,9 @@
      * permissions, or channel in use.
      * @hide
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothServerSocket listenUsingRfcommOn(int channel) throws IOException {
         return listenUsingRfcommOn(channel, false, false);
     }
@@ -2505,7 +2601,6 @@
      * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming
      * connections from a listening {@link BluetoothServerSocket}.
      * <p>Valid RFCOMM channels are in range 1 to 30.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
      * <p>To auto assign a channel without creating a SDP record use
      * {@link #SOCKET_CHANNEL_AUTO_STATIC_NO_SDP} as channel number.
      *
@@ -2519,6 +2614,9 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothServerSocket listenUsingRfcommOn(int channel, boolean mitm,
             boolean min16DigitPin) throws IOException {
         BluetoothServerSocket socket =
@@ -2559,7 +2657,9 @@
      * @throws IOException on error, for example Bluetooth not available, or insufficient
      * permissions, or channel in use.
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothServerSocket listenUsingRfcommWithServiceRecord(String name, UUID uuid)
             throws IOException {
         return createNewRfcommSocketAndRecord(name, uuid, true, true);
@@ -2591,7 +2691,9 @@
      * @throws IOException on error, for example Bluetooth not available, or insufficient
      * permissions, or channel in use.
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(String name, UUID uuid)
             throws IOException {
         return createNewRfcommSocketAndRecord(name, uuid, false, false);
@@ -2622,7 +2724,6 @@
      * closed, or if this application closes unexpectedly.
      * <p>Use {@link BluetoothDevice#createRfcommSocketToServiceRecord} to
      * connect to this socket from another device using the same {@link UUID}.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
      *
      * @param name service name for SDP record
      * @param uuid uuid for SDP record
@@ -2632,12 +2733,15 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothServerSocket listenUsingEncryptedRfcommWithServiceRecord(String name, UUID uuid)
             throws IOException {
         return createNewRfcommSocketAndRecord(name, uuid, false, true);
     }
 
-
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     private BluetoothServerSocket createNewRfcommSocketAndRecord(String name, UUID uuid,
             boolean auth, boolean encrypt) throws IOException {
         BluetoothServerSocket socket;
@@ -2663,6 +2767,7 @@
      * permissions.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothServerSocket listenUsingInsecureRfcommOn(int port) throws IOException {
         BluetoothServerSocket socket =
                 new BluetoothServerSocket(BluetoothSocket.TYPE_RFCOMM, false, false, port);
@@ -2694,6 +2799,7 @@
      * permissions.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothServerSocket listenUsingL2capOn(int port, boolean mitm, boolean min16DigitPin)
             throws IOException {
         BluetoothServerSocket socket =
@@ -2726,11 +2832,11 @@
      * permissions.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothServerSocket listenUsingL2capOn(int port) throws IOException {
         return listenUsingL2capOn(port, false, false);
     }
 
-
     /**
      * Construct an insecure L2CAP server socket.
      * Call #accept to retrieve connections to this socket.
@@ -2743,6 +2849,7 @@
      * permissions.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothServerSocket listenUsingInsecureL2capOn(int port) throws IOException {
         Log.d(TAG, "listenUsingInsecureL2capOn: port=" + port);
         BluetoothServerSocket socket =
@@ -2769,11 +2876,14 @@
 
     /**
      * Read the local Out of Band Pairing Data
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
      *
      * @return Pair<byte[], byte[]> of Hash and Randomizer
      * @hide
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public Pair<byte[], byte[]> readOutOfBandData() {
         return null;
     }
@@ -2863,6 +2973,7 @@
      * @param profile
      * @param proxy Profile proxy object
      */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public void closeProfileProxy(int profile, BluetoothProfile proxy) {
         if (proxy == null) {
             return;
@@ -2937,6 +3048,7 @@
 
     private final IBluetoothManagerCallback mManagerCallback =
             new IBluetoothManagerCallback.Stub() {
+                @SuppressLint("AndroidFrameworkRequiresPermission")
                 public void onBluetoothServiceUp(IBluetooth bluetoothService) {
                     if (DBG) {
                         Log.d(TAG, "onBluetoothServiceUp: " + bluetoothService);
@@ -3031,7 +3143,9 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean enableNoAutoConnect() {
         if (isEnabled()) {
             if (DBG) {
@@ -3184,7 +3298,7 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public void generateLocalOobData(@Transport int transport,
             @NonNull @CallbackExecutor Executor executor, @NonNull OobDataCallback callback) {
         if (transport != BluetoothDevice.TRANSPORT_BREDR && transport
@@ -3228,12 +3342,14 @@
      * reason. If Bluetooth is already on and if this function is called to turn
      * it on, the api will return true and a callback will be called.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
-     *
      * @param on True for on, false for off.
      * @param callback The callback to notify changes to the state.
      * @hide
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public boolean changeApplicationBluetoothState(boolean on,
             BluetoothStateChangeCallback callback) {
         return false;
@@ -3252,6 +3368,7 @@
     /**
      * @hide
      */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public class StateChangeCallbackWrapper extends IBluetoothStateChangeCallback.Stub {
         private BluetoothStateChangeCallback mCallback;
 
@@ -3443,7 +3560,10 @@
      * instead.
      */
     @Deprecated
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothScanPermission
+    @RequiresBluetoothLocationPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     public boolean startLeScan(LeScanCallback callback) {
         return startLeScan(null, callback);
     }
@@ -3462,7 +3582,10 @@
      * instead.
      */
     @Deprecated
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothScanPermission
+    @RequiresBluetoothLocationPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     public boolean startLeScan(final UUID[] serviceUuids, final LeScanCallback callback) {
         if (DBG) {
             Log.d(TAG, "startLeScan(): " + Arrays.toString(serviceUuids));
@@ -3559,7 +3682,9 @@
      * @deprecated Use {@link BluetoothLeScanner#stopScan(ScanCallback)} instead.
      */
     @Deprecated
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothScanPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     public void stopLeScan(LeScanCallback callback) {
         if (DBG) {
             Log.d(TAG, "stopLeScan()");
@@ -3600,7 +3725,9 @@
      * @throws IOException on error, for example Bluetooth not available, or insufficient
      * permissions, or unable to start this CoC
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @NonNull BluetoothServerSocket listenUsingL2capChannel()
             throws IOException {
         BluetoothServerSocket socket =
@@ -3646,7 +3773,9 @@
      * @throws IOException on error, for example Bluetooth not available, or insufficient
      * permissions, or unable to start this CoC
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @NonNull BluetoothServerSocket listenUsingInsecureL2capChannel()
             throws IOException {
         BluetoothServerSocket socket =
@@ -3691,7 +3820,7 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean addOnMetadataChangedListener(@NonNull BluetoothDevice device,
             @NonNull Executor executor, @NonNull OnMetadataChangedListener listener) {
         if (DBG) Log.d(TAG, "addOnMetadataChangedListener()");
@@ -3764,7 +3893,7 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean removeOnMetadataChangedListener(@NonNull BluetoothDevice device,
             @NonNull OnMetadataChangedListener listener) {
         if (DBG) Log.d(TAG, "removeOnMetadataChangedListener()");
@@ -3853,6 +3982,7 @@
      * @throws IllegalArgumentException if the callback is already registered
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean registerBluetoothConnectionCallback(@NonNull @CallbackExecutor Executor executor,
             @NonNull BluetoothConnectionCallback callback) {
         if (DBG) Log.d(TAG, "registerBluetoothConnectionCallback()");
@@ -3895,6 +4025,7 @@
      * @return true if the callback was unregistered successfully, false otherwise
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean unregisterBluetoothConnectionCallback(
             @NonNull BluetoothConnectionCallback callback) {
         if (DBG) Log.d(TAG, "unregisterBluetoothConnectionCallback()");
diff --git a/core/java/android/bluetooth/BluetoothAvrcpController.java b/core/java/android/bluetooth/BluetoothAvrcpController.java
index 4e7e441..887cf3f 100644
--- a/core/java/android/bluetooth/BluetoothAvrcpController.java
+++ b/core/java/android/bluetooth/BluetoothAvrcpController.java
@@ -16,6 +16,10 @@
 
 package android.bluetooth;
 
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.content.Context;
 import android.os.Binder;
 import android.os.IBinder;
@@ -54,10 +58,10 @@
      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public static final String ACTION_CONNECTION_STATE_CHANGED =
             "android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED";
 
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 0c208fd..1201663 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -16,7 +16,6 @@
 
 package android.bluetooth;
 
-import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -26,6 +25,11 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.app.PropertyInvalidatedCache;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
+import android.bluetooth.annotations.RequiresBluetoothScanPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.companion.AssociationRequest;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -66,9 +70,6 @@
  * {@link #createRfcommSocketToServiceRecord(UUID)} over Bluetooth BR/EDR or using
  * {@link #createL2capChannel(int)} over Bluetooth LE.
  *
- * <p class="note"><strong>Note:</strong>
- * Requires the {@link android.Manifest.permission#BLUETOOTH} permission.
- *
  * <div class="special reference">
  * <h3>Developer Guides</h3>
  * <p>
@@ -108,10 +109,12 @@
      * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link
      * #EXTRA_CLASS}. Can contain the extra fields {@link #EXTRA_NAME} and/or
      * {@link #EXTRA_RSSI} if they are available.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} and
-     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} to receive.
      */
     // TODO: Change API to not broadcast RSSI if not available (incoming connection)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothScanPermission
+    @RequiresBluetoothLocationPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_FOUND =
             "android.bluetooth.device.action.FOUND";
@@ -120,9 +123,11 @@
      * Broadcast Action: Bluetooth class of a remote device has changed.
      * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link
      * #EXTRA_CLASS}.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
      * {@see BluetoothClass}
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_CLASS_CHANGED =
             "android.bluetooth.device.action.CLASS_CHANGED";
@@ -133,8 +138,10 @@
      * <p>Always contains the extra field {@link #EXTRA_DEVICE}.
      * <p>ACL connections are managed automatically by the Android Bluetooth
      * stack.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_ACL_CONNECTED =
             "android.bluetooth.device.action.ACL_CONNECTED";
@@ -146,8 +153,10 @@
      * this intent as a hint to immediately terminate higher level connections
      * (RFCOMM, L2CAP, or profile connections) to the remote device.
      * <p>Always contains the extra field {@link #EXTRA_DEVICE}.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_ACL_DISCONNECT_REQUESTED =
             "android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED";
@@ -158,8 +167,10 @@
      * <p>Always contains the extra field {@link #EXTRA_DEVICE}.
      * <p>ACL connections are managed automatically by the Android Bluetooth
      * stack.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_ACL_DISCONNECTED =
             "android.bluetooth.device.action.ACL_DISCONNECTED";
@@ -169,8 +180,10 @@
      * been retrieved for the first time, or changed since the last retrieval.
      * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link
      * #EXTRA_NAME}.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_NAME_CHANGED =
             "android.bluetooth.device.action.NAME_CHANGED";
@@ -179,9 +192,11 @@
      * Broadcast Action: Indicates the alias of a remote device has been
      * changed.
      * <p>Always contains the extra field {@link #EXTRA_DEVICE}.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
      */
     @SuppressLint("ActionValue")
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_ALIAS_CHANGED =
             "android.bluetooth.device.action.ALIAS_CHANGED";
@@ -191,10 +206,12 @@
      * device. For example, if a device is bonded (paired).
      * <p>Always contains the extra fields {@link #EXTRA_DEVICE}, {@link
      * #EXTRA_BOND_STATE} and {@link #EXTRA_PREVIOUS_BOND_STATE}.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
      */
     // Note: When EXTRA_BOND_STATE is BOND_NONE then this will also
     // contain a hidden extra field EXTRA_REASON with the result code.
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_BOND_STATE_CHANGED =
             "android.bluetooth.device.action.BOND_STATE_CHANGED";
@@ -204,10 +221,12 @@
      * been retrieved for the first time, or changed since the last retrieval
      * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link
      * #EXTRA_BATTERY_LEVEL}.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
      *
      * @hide
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_BATTERY_LEVEL_CHANGED =
             "android.bluetooth.device.action.BATTERY_LEVEL_CHANGED";
@@ -642,8 +661,10 @@
      * device are requested to be fetched using Service Discovery Protocol
      * <p> Always contains the extra field {@link #EXTRA_DEVICE}
      * <p> Always contains the extra field {@link #EXTRA_UUID}
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} to receive.
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothScanPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_UUID =
             "android.bluetooth.device.action.UUID";
@@ -657,20 +678,23 @@
      * Broadcast Action: Indicates a failure to retrieve the name of a remote
      * device.
      * <p>Always contains the extra field {@link #EXTRA_DEVICE}.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
      *
      * @hide
      */
     //TODO: is this actually useful?
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_NAME_FAILED =
             "android.bluetooth.device.action.NAME_FAILED";
 
     /**
      * Broadcast Action: This intent is used to broadcast PAIRING REQUEST
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} to
-     * receive.
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothScanPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_PAIRING_REQUEST =
             "android.bluetooth.device.action.PAIRING_REQUEST";
@@ -1206,7 +1230,9 @@
      *
      * @return the Bluetooth name, or null if there was a problem.
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public String getName() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1235,7 +1261,9 @@
      * @return the device type {@link #DEVICE_TYPE_CLASSIC}, {@link #DEVICE_TYPE_LE} {@link
      * #DEVICE_TYPE_DUAL}. {@link #DEVICE_TYPE_UNKNOWN} if it's not available
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getType() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1257,7 +1285,9 @@
      * null if there was a problem
      */
     @Nullable
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public String getAlias() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1293,7 +1323,9 @@
      * @return {@code true} if the alias is successfully set, {@code false} on error
      * @throws IllegalArgumentException if the alias is {@code null} or the empty string
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean setAlias(@NonNull String alias) {
         if (alias == null || alias.isEmpty()) {
             throw new IllegalArgumentException("Cannot set the alias to null or the empty string");
@@ -1321,7 +1353,9 @@
      * @hide
      */
     @UnsupportedAppUsage
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getBatteryLevel() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1346,7 +1380,9 @@
      *
      * @return false on immediate error, true if bonding will begin
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean createBond() {
         return createBond(TRANSPORT_AUTO);
     }
@@ -1367,7 +1403,9 @@
      * @hide
      */
     @UnsupportedAppUsage
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean createBond(int transport) {
         return createBondInternal(transport, null, null);
     }
@@ -1395,7 +1433,7 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean createBondOutOfBand(int transport, @Nullable OobData remoteP192Data,
             @Nullable OobData remoteP256Data) {
         if (remoteP192Data == null && remoteP256Data == null) {
@@ -1406,6 +1444,7 @@
         return createBondInternal(transport, remoteP192Data, remoteP256Data);
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     private boolean createBondInternal(int transport, @Nullable OobData remoteP192Data,
             @Nullable OobData remoteP256Data) {
         final IBluetooth service = sService;
@@ -1430,7 +1469,9 @@
      * @hide
      */
     @UnsupportedAppUsage
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean isBondingInitiatedLocally() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1452,7 +1493,7 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean cancelBondProcess() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1480,7 +1521,7 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean removeBond() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1504,6 +1545,7 @@
             new PropertyInvalidatedCache<BluetoothDevice, Integer>(
                 8, BLUETOOTH_BONDING_CACHE_PROPERTY) {
                 @Override
+                @SuppressLint("AndroidFrameworkRequiresPermission")
                 protected Integer recompute(BluetoothDevice query) {
                     try {
                         return sService.getBondState(query);
@@ -1532,7 +1574,10 @@
      *
      * @return the bond state
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public int getBondState() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1560,7 +1605,7 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean canBondWithoutDialog() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1583,7 +1628,9 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean isConnected() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1606,7 +1653,9 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean isEncrypted() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1626,7 +1675,9 @@
      *
      * @return Bluetooth class object, or null on error
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothClass getBluetoothClass() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1653,7 +1704,9 @@
      *
      * @return the supported features (UUIDs) of the remote device, or null on error
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public ParcelUuid[] getUuids() {
         final IBluetooth service = sService;
         if (service == null || !isBluetoothEnabled()) {
@@ -1681,7 +1734,9 @@
      * @return False if the check fails, True if the process of initiating an ACL connection
      * to the remote device was started.
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean fetchUuidsWithSdp() {
         final IBluetooth service = sService;
         if (service == null || !isBluetoothEnabled()) {
@@ -1707,8 +1762,7 @@
      * {@link #EXTRA_SDP_SEARCH_STATUS} different from 0.
      * Detailed status error codes can be found by members of the Bluetooth package in
      * the AbstractionLayer class.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
-     * The SDP record data will be stored in the intent as {@link #EXTRA_SDP_RECORD}.
+     * <p>The SDP record data will be stored in the intent as {@link #EXTRA_SDP_RECORD}.
      * The object type will match one of the SdpXxxRecord types, depending on the UUID searched
      * for.
      *
@@ -1717,6 +1771,9 @@
      *               was started.
      */
     /** @hide */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean sdpSearch(ParcelUuid uuid) {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1733,10 +1790,12 @@
 
     /**
      * Set the pin during pairing when the pairing method is {@link #PAIRING_VARIANT_PIN}
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
      *
      * @return true pin has been set false for error
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean setPin(byte[] pin) {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1758,7 +1817,9 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean setPin(@NonNull String pin) {
         byte[] pinBytes = convertPinToBytes(pin);
         if (pinBytes == null) {
@@ -1772,7 +1833,7 @@
      *
      * @return true confirmation has been sent out false for error
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setPairingConfirmation(boolean confirm) {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1795,7 +1856,9 @@
      * @hide
      */
     @UnsupportedAppUsage
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean cancelPairing() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1827,7 +1890,9 @@
      * @hide
      */
     @UnsupportedAppUsage
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @AccessPermission int getPhonebookAccessPermission() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1859,8 +1924,6 @@
      * If the {@link BluetoothDevice} is not connected with A2DP or HFP, it cannot
      * enter silence mode.
      *
-     * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
-     *
      * @param silence true to enter silence mode, false to exit
      * @return true on success, false on error.
      * @throws IllegalStateException if Bluetooth is not turned ON.
@@ -1884,8 +1947,6 @@
     /**
      * Check whether the {@link BluetoothDevice} is in silence mode
      *
-     * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
-     *
      * @return true on device in silence mode, otherwise false.
      * @throws IllegalStateException if Bluetooth is not turned ON.
      * @hide
@@ -1935,7 +1996,9 @@
      * @hide
      */
     @UnsupportedAppUsage
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @AccessPermission int getMessageAccessPermission() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1959,7 +2022,7 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setMessageAccessPermission(@AccessPermission int value) {
         // Validates param value is one of the accepted constants
         if (value != ACCESS_ALLOWED && value != ACCESS_REJECTED && value != ACCESS_UNKNOWN) {
@@ -1984,7 +2047,9 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @AccessPermission int getSimAccessPermission() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -2008,7 +2073,7 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setSimAccessPermission(int value) {
         final IBluetooth service = sService;
         if (service == null) {
@@ -2039,7 +2104,6 @@
      * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
      * connection.
      * <p>Valid RFCOMM channels are in range 1 to 30.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
      *
      * @param channel RFCOMM channel to connect to
      * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
@@ -2048,6 +2112,10 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public BluetoothSocket createRfcommSocket(int channel) throws IOException {
         if (!isBluetoothEnabled()) {
             Log.e(TAG, "Bluetooth is not enabled");
@@ -2074,7 +2142,6 @@
      * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
      * connection.
      * <p>Valid L2CAP PSM channels are in range 1 to 2^16.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
      *
      * @param channel L2cap PSM/channel to connect to
      * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
@@ -2082,6 +2149,10 @@
      * permissions
      * @hide
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public BluetoothSocket createL2capSocket(int channel) throws IOException {
         return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP, -1, true, true, this, channel,
                 null);
@@ -2095,7 +2166,6 @@
      * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
      * connection.
      * <p>Valid L2CAP PSM channels are in range 1 to 2^16.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
      *
      * @param channel L2cap PSM/channel to connect to
      * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
@@ -2103,6 +2173,10 @@
      * permissions
      * @hide
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public BluetoothSocket createInsecureL2capSocket(int channel) throws IOException {
         return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP, -1, false, false, this, channel,
                 null);
@@ -2138,7 +2212,10 @@
      * @throws IOException on error, for example Bluetooth not available, or insufficient
      * permissions
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid) throws IOException {
         if (!isBluetoothEnabled()) {
             Log.e(TAG, "Bluetooth is not enabled");
@@ -2176,7 +2253,10 @@
      * @throws IOException on error, for example Bluetooth not available, or insufficient
      * permissions
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public BluetoothSocket createInsecureRfcommSocketToServiceRecord(UUID uuid) throws IOException {
         if (!isBluetoothEnabled()) {
             Log.e(TAG, "Bluetooth is not enabled");
@@ -2192,7 +2272,6 @@
      * Call #connect on the returned #BluetoothSocket to begin the connection.
      * The remote device will not be authenticated and communication on this
      * socket will not be encrypted.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
      *
      * @param port remote port
      * @return An RFCOMM BluetoothSocket
@@ -2202,6 +2281,10 @@
      */
     @UnsupportedAppUsage(publicAlternatives = "Use "
             + "{@link #createInsecureRfcommSocketToServiceRecord} instead.")
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public BluetoothSocket createInsecureRfcommSocket(int port) throws IOException {
         if (!isBluetoothEnabled()) {
             Log.e(TAG, "Bluetooth is not enabled");
@@ -2214,7 +2297,6 @@
     /**
      * Construct a SCO socket ready to start an outgoing connection.
      * Call #connect on the returned #BluetoothSocket to begin the connection.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
      *
      * @return a SCO BluetoothSocket
      * @throws IOException on error, for example Bluetooth not available, or insufficient
@@ -2222,6 +2304,10 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public BluetoothSocket createScoSocket() throws IOException {
         if (!isBluetoothEnabled()) {
             Log.e(TAG, "Bluetooth is not enabled");
@@ -2269,6 +2355,8 @@
      * automatically connect as soon as the remote device becomes available (true).
      * @throws IllegalArgumentException if callback is null
      */
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothGatt connectGatt(Context context, boolean autoConnect,
             BluetoothGattCallback callback) {
         return (connectGatt(context, autoConnect, callback, TRANSPORT_AUTO));
@@ -2289,6 +2377,8 @@
      * BluetoothDevice#TRANSPORT_LE}
      * @throws IllegalArgumentException if callback is null
      */
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothGatt connectGatt(Context context, boolean autoConnect,
             BluetoothGattCallback callback, int transport) {
         return (connectGatt(context, autoConnect, callback, transport, PHY_LE_1M_MASK));
@@ -2313,6 +2403,8 @@
      * is set to true.
      * @throws NullPointerException if callback is null
      */
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothGatt connectGatt(Context context, boolean autoConnect,
             BluetoothGattCallback callback, int transport, int phy) {
         return connectGatt(context, autoConnect, callback, transport, phy, null);
@@ -2339,6 +2431,8 @@
      * an un-specified background thread.
      * @throws NullPointerException if callback is null
      */
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothGatt connectGatt(Context context, boolean autoConnect,
             BluetoothGattCallback callback, int transport, int phy,
             Handler handler) {
@@ -2372,6 +2466,8 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothGatt connectGatt(Context context, boolean autoConnect,
             BluetoothGattCallback callback, int transport,
             boolean opportunistic, int phy, Handler handler) {
@@ -2416,7 +2512,10 @@
      * @throws IOException on error, for example Bluetooth not available, or insufficient
      * permissions
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public @NonNull BluetoothSocket createL2capChannel(int psm) throws IOException {
         if (!isBluetoothEnabled()) {
             Log.e(TAG, "createL2capChannel: Bluetooth is not enabled");
@@ -2444,7 +2543,10 @@
      * @throws IOException on error, for example Bluetooth not available, or insufficient
      * permissions
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public @NonNull BluetoothSocket createInsecureL2capChannel(int psm) throws IOException {
         if (!isBluetoothEnabled()) {
             Log.e(TAG, "createInsecureL2capChannel: Bluetooth is not enabled");
@@ -2472,7 +2574,7 @@
      * @hide
     */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setMetadata(@MetadataKey int key, @NonNull byte[] value) {
         final IBluetooth service = sService;
         if (service == null) {
@@ -2500,7 +2602,7 @@
      */
     @SystemApi
     @Nullable
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public byte[] getMetadata(@MetadataKey int key) {
         final IBluetooth service = sService;
         if (service == null) {
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 381318b..942f843 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -17,6 +17,14 @@
 package android.bluetooth;
 
 import android.compat.annotation.UnsupportedAppUsage;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.RequiresPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
+import android.bluetooth.annotations.RequiresBluetoothScanPermission;
 import android.os.Build;
 import android.os.Handler;
 import android.os.ParcelUuid;
@@ -157,6 +165,7 @@
                  * @hide
                  */
                 @Override
+                @SuppressLint("AndroidFrameworkRequiresPermission")
                 public void onClientRegistered(int status, int clientIf) {
                     if (DBG) {
                         Log.d(TAG, "onClientRegistered() - status=" + status
@@ -347,6 +356,7 @@
                  * @hide
                  */
                 @Override
+                @SuppressLint("AndroidFrameworkRequiresPermission")
                 public void onCharacteristicRead(String address, int status, int handle,
                         byte[] value) {
                     if (VDBG) {
@@ -404,6 +414,7 @@
                  * @hide
                  */
                 @Override
+                @SuppressLint("AndroidFrameworkRequiresPermission")
                 public void onCharacteristicWrite(String address, int status, int handle) {
                     if (VDBG) {
                         Log.d(TAG, "onCharacteristicWrite() - Device=" + address
@@ -487,6 +498,7 @@
                  * @hide
                  */
                 @Override
+                @SuppressLint("AndroidFrameworkRequiresPermission")
                 public void onDescriptorRead(String address, int status, int handle, byte[] value) {
                     if (VDBG) {
                         Log.d(TAG,
@@ -538,6 +550,7 @@
                  * @hide
                  */
                 @Override
+                @SuppressLint("AndroidFrameworkRequiresPermission")
                 public void onDescriptorWrite(String address, int status, int handle) {
                     if (VDBG) {
                         Log.d(TAG,
@@ -734,6 +747,7 @@
      * Application should call this method as early as possible after it is done with
      * this GATT client.
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void close() {
         if (DBG) Log.d(TAG, "close()");
 
@@ -817,12 +831,13 @@
      * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered}
      * is used to notify success or failure if the function returns true.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param callback GATT callback handler that will receive asynchronous callbacks.
      * @return If true, the callback will be called to notify success or failure, false on immediate
      * error
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     private boolean registerApp(BluetoothGattCallback callback, Handler handler) {
         return registerApp(callback, handler, false);
     }
@@ -833,14 +848,15 @@
      * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered}
      * is used to notify success or failure if the function returns true.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param callback GATT callback handler that will receive asynchronous callbacks.
      * @param eatt_support indicate to allow for eatt support
      * @return If true, the callback will be called to notify success or failure, false on immediate
      * error
      * @hide
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     private boolean registerApp(BluetoothGattCallback callback, Handler handler,
                                 boolean eatt_support) {
         if (DBG) Log.d(TAG, "registerApp()");
@@ -865,6 +881,7 @@
      * Unregister the current application and callbacks.
      */
     @UnsupportedAppUsage
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     private void unregisterApp() {
         if (DBG) Log.d(TAG, "unregisterApp() - mClientIf=" + mClientIf);
         if (mService == null || mClientIf == 0) return;
@@ -893,14 +910,15 @@
      * subsequent connections to known devices should be invoked with the
      * autoConnect parameter set to true.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param device Remote device to connect to
      * @param autoConnect Whether to directly connect to the remote device (false) or to
      * automatically connect as soon as the remote device becomes available (true).
      * @return true, if the connection attempt was initiated successfully
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     /*package*/ boolean connect(Boolean autoConnect, BluetoothGattCallback callback,
             Handler handler) {
         if (DBG) {
@@ -931,9 +949,10 @@
     /**
      * Disconnects an established connection, or cancels a connection attempt
      * currently in progress.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void disconnect() {
         if (DBG) Log.d(TAG, "cancelOpen() - device: " + mDevice.getAddress());
         if (mService == null || mClientIf == 0) return;
@@ -954,6 +973,7 @@
      *
      * @return true, if the connection attempt was initiated successfully
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean connect() {
         try {
             mService.clientConnect(mClientIf, mDevice.getAddress(), false, mTransport,
@@ -983,6 +1003,7 @@
      * of {@link BluetoothDevice#PHY_OPTION_NO_PREFERRED}, {@link BluetoothDevice#PHY_OPTION_S2} or
      * {@link BluetoothDevice#PHY_OPTION_S8}
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void setPreferredPhy(int txPhy, int rxPhy, int phyOptions) {
         try {
             mService.clientSetPreferredPhy(mClientIf, mDevice.getAddress(), txPhy, rxPhy,
@@ -996,6 +1017,7 @@
      * Read the current transmitter PHY and receiver PHY of the connection. The values are returned
      * in {@link BluetoothGattCallback#onPhyRead}
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void readPhy() {
         try {
             mService.clientReadPhy(mClientIf, mDevice.getAddress());
@@ -1022,10 +1044,11 @@
      * triggered. If the discovery was successful, the remote services can be
      * retrieved using the {@link #getServices} function.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @return true, if the remote service discovery has been started
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean discoverServices() {
         if (DBG) Log.d(TAG, "discoverServices() - device: " + mDevice.getAddress());
         if (mService == null || mClientIf == 0) return false;
@@ -1047,11 +1070,12 @@
      * It should never be used by real applications. The service is not searched
      * for characteristics and descriptors, or returned in any callback.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @return true, if the remote service discovery has been started
      * @hide
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean discoverServiceByUuid(UUID uuid) {
         if (DBG) Log.d(TAG, "discoverServiceByUuid() - device: " + mDevice.getAddress());
         if (mService == null || mClientIf == 0) return false;
@@ -1073,11 +1097,10 @@
      * <p>This function requires that service discovery has been completed
      * for the given device.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @return List of services on the remote device. Returns an empty list if service discovery has
      * not yet been performed.
      */
+    @RequiresLegacyBluetoothPermission
     public List<BluetoothGattService> getServices() {
         List<BluetoothGattService> result =
                 new ArrayList<BluetoothGattService>();
@@ -1101,12 +1124,11 @@
      * <p>If multiple instances of the same service (as identified by UUID)
      * exist, the first instance of the service is returned.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param uuid UUID of the requested service
      * @return BluetoothGattService if supported, or null if the requested service is not offered by
      * the remote device.
      */
+    @RequiresLegacyBluetoothPermission
     public BluetoothGattService getService(UUID uuid) {
         for (BluetoothGattService service : mServices) {
             if (service.getDevice().equals(mDevice) && service.getUuid().equals(uuid)) {
@@ -1124,11 +1146,12 @@
      * is reported by the {@link BluetoothGattCallback#onCharacteristicRead}
      * callback.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param characteristic Characteristic to read from the remote device
      * @return true, if the read operation was initiated successfully
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
         if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) == 0) {
             return false;
@@ -1167,12 +1190,13 @@
      * is reported by the {@link BluetoothGattCallback#onCharacteristicRead}
      * callback.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param uuid UUID of characteristic to read from the remote device
      * @return true, if the read operation was initiated successfully
      * @hide
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean readUsingCharacteristicUuid(UUID uuid, int startHandle, int endHandle) {
         if (VDBG) Log.d(TAG, "readUsingCharacteristicUuid() - uuid: " + uuid);
         if (mService == null || mClientIf == 0) return false;
@@ -1202,11 +1226,12 @@
      * {@link BluetoothGattCallback#onCharacteristicWrite} callback is invoked,
      * reporting the result of the operation.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param characteristic Characteristic to write on the remote device
      * @return true, if the write operation was initiated successfully
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
         if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0
                 && (characteristic.getProperties()
@@ -1248,11 +1273,12 @@
      * {@link BluetoothGattCallback#onDescriptorRead} callback is
      * triggered, signaling the result of the operation.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param descriptor Descriptor value to read from the remote device
      * @return true, if the read operation was initiated successfully
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean readDescriptor(BluetoothGattDescriptor descriptor) {
         if (VDBG) Log.d(TAG, "readDescriptor() - uuid: " + descriptor.getUuid());
         if (mService == null || mClientIf == 0) return false;
@@ -1289,11 +1315,12 @@
      * <p>A {@link BluetoothGattCallback#onDescriptorWrite} callback is
      * triggered to report the result of the write operation.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param descriptor Descriptor to write to the associated remote device
      * @return true, if the write operation was initiated successfully
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean writeDescriptor(BluetoothGattDescriptor descriptor) {
         if (VDBG) Log.d(TAG, "writeDescriptor() - uuid: " + descriptor.getUuid());
         if (mService == null || mClientIf == 0 || descriptor.getValue() == null) return false;
@@ -1340,10 +1367,11 @@
      * cancel the current transaction without committing any values on the
      * remote device.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @return true, if the reliable write transaction has been initiated
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean beginReliableWrite() {
         if (VDBG) Log.d(TAG, "beginReliableWrite() - device: " + mDevice.getAddress());
         if (mService == null || mClientIf == 0) return false;
@@ -1367,10 +1395,11 @@
      * <p>A {@link BluetoothGattCallback#onReliableWriteCompleted} callback is
      * invoked to indicate whether the transaction has been executed correctly.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @return true, if the request to execute the transaction has been sent
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean executeReliableWrite() {
         if (VDBG) Log.d(TAG, "executeReliableWrite() - device: " + mDevice.getAddress());
         if (mService == null || mClientIf == 0) return false;
@@ -1396,9 +1425,10 @@
      *
      * <p>Calling this function will discard all queued characteristic write
      * operations for a given remote device.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void abortReliableWrite() {
         if (VDBG) Log.d(TAG, "abortReliableWrite() - device: " + mDevice.getAddress());
         if (mService == null || mClientIf == 0) return;
@@ -1414,6 +1444,7 @@
      * @deprecated Use {@link #abortReliableWrite()}
      */
     @Deprecated
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void abortReliableWrite(BluetoothDevice mDevice) {
         abortReliableWrite();
     }
@@ -1426,12 +1457,13 @@
      * triggered if the remote device indicates that the given characteristic
      * has changed.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param characteristic The characteristic for which to enable notifications
      * @param enable Set to true to enable notifications/indications
      * @return true, if the requested notification status was set successfully
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
             boolean enable) {
         if (DBG) {
@@ -1464,6 +1496,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean refresh() {
         if (DBG) Log.d(TAG, "refresh() - device: " + mDevice.getAddress());
         if (mService == null || mClientIf == 0) return false;
@@ -1484,10 +1517,11 @@
      * <p>The {@link BluetoothGattCallback#onReadRemoteRssi} callback will be
      * invoked when the RSSI value has been read.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @return true, if the RSSI value has been requested successfully
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean readRemoteRssi() {
         if (DBG) Log.d(TAG, "readRssi() - device: " + mDevice.getAddress());
         if (mService == null || mClientIf == 0) return false;
@@ -1512,10 +1546,11 @@
      * <p>A {@link BluetoothGattCallback#onMtuChanged} callback will indicate
      * whether this operation was successful.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @return true, if the new MTU value has been requested successfully
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean requestMtu(int mtu) {
         if (DBG) {
             Log.d(TAG, "configureMTU() - device: " + mDevice.getAddress()
@@ -1544,6 +1579,7 @@
      * or {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}.
      * @throws IllegalArgumentException If the parameters are outside of their specified range.
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean requestConnectionPriority(int connectionPriority) {
         if (connectionPriority < CONNECTION_PRIORITY_BALANCED
                 || connectionPriority > CONNECTION_PRIORITY_LOW_POWER) {
@@ -1571,6 +1607,7 @@
      * @return true, if the request is send to the Bluetooth stack.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean requestLeConnectionUpdate(int minConnectionInterval, int maxConnectionInterval,
                                              int slaveLatency, int supervisionTimeout,
                                              int minConnectionEventLen, int maxConnectionEventLen) {
diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
index 8f1b59c..8a7d4ba 100644
--- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java
+++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
@@ -237,7 +237,6 @@
 
     /**
      * Create a new BluetoothGattCharacteristic.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
      *
      * @param uuid The UUID for this characteristic
      * @param properties Properties of this characteristic
@@ -344,7 +343,6 @@
 
     /**
      * Adds a descriptor to this characteristic.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
      *
      * @param descriptor Descriptor to be added to this characteristic.
      * @return true, if the descriptor was added to the characteristic
diff --git a/core/java/android/bluetooth/BluetoothGattDescriptor.java b/core/java/android/bluetooth/BluetoothGattDescriptor.java
index 49ba281..ed5ea08 100644
--- a/core/java/android/bluetooth/BluetoothGattDescriptor.java
+++ b/core/java/android/bluetooth/BluetoothGattDescriptor.java
@@ -128,7 +128,6 @@
 
     /**
      * Create a new BluetoothGattDescriptor.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
      *
      * @param uuid The UUID for this descriptor
      * @param permissions Permissions for this descriptor
@@ -139,7 +138,6 @@
 
     /**
      * Create a new BluetoothGattDescriptor.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
      *
      * @param characteristic The characteristic this descriptor belongs to
      * @param uuid The UUID for this descriptor
@@ -228,8 +226,6 @@
      * <p>If a remote device offers multiple descriptors with the same UUID,
      * the instance ID is used to distuinguish between descriptors.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @return Instance ID of this descriptor
      * @hide
      */
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index 088b016..fdb8018 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -16,6 +16,9 @@
 
 package android.bluetooth;
 
+import android.annotation.RequiresPermission;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.util.Log;
@@ -425,6 +428,7 @@
      * Application should call this method as early as possible after it is done with
      * this GATT server.
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void close() {
         if (DBG) Log.d(TAG, "close()");
         unregisterCallback();
@@ -436,12 +440,13 @@
      * <p>This is an asynchronous call. The callback is used to notify
      * success or failure if the function returns true.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param callback GATT callback handler that will receive asynchronous callbacks.
      * @return true, the callback will be called to notify success or failure, false on immediate
      * error
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     /*package*/ boolean registerCallback(BluetoothGattServerCallback callback) {
         return registerCallback(callback, false);
     }
@@ -452,14 +457,15 @@
      * <p>This is an asynchronous call. The callback is used to notify
      * success or failure if the function returns true.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param callback GATT callback handler that will receive asynchronous callbacks.
      * @param eatt_support indicates if server can use eatt
      * @return true, the callback will be called to notify success or failure, false on immediate
      * error
      * @hide
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     /*package*/ boolean registerCallback(BluetoothGattServerCallback callback,
                                          boolean eatt_support) {
         if (DBG) Log.d(TAG, "registerCallback()");
@@ -504,6 +510,7 @@
     /**
      * Unregister the current application and callbacks.
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     private void unregisterCallback() {
         if (DBG) Log.d(TAG, "unregisterCallback() - mServerIf=" + mServerIf);
         if (mService == null || mServerIf == 0) return;
@@ -548,12 +555,13 @@
      * subsequent connections to known devices should be invoked with the
      * autoConnect parameter set to true.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param autoConnect Whether to directly connect to the remote device (false) or to
      * automatically connect as soon as the remote device becomes available (true).
      * @return true, if the connection attempt was initiated successfully
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean connect(BluetoothDevice device, boolean autoConnect) {
         if (DBG) {
             Log.d(TAG,
@@ -576,10 +584,11 @@
      * Disconnects an established connection, or cancels a connection attempt
      * currently in progress.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param device Remote device
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void cancelConnection(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "cancelConnection() - device: " + device.getAddress());
         if (mService == null || mServerIf == 0) return;
@@ -609,6 +618,7 @@
      * of {@link BluetoothDevice#PHY_OPTION_NO_PREFERRED}, {@link BluetoothDevice#PHY_OPTION_S2} or
      * {@link BluetoothDevice#PHY_OPTION_S8}
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void setPreferredPhy(BluetoothDevice device, int txPhy, int rxPhy, int phyOptions) {
         try {
             mService.serverSetPreferredPhy(mServerIf, device.getAddress(), txPhy, rxPhy,
@@ -624,6 +634,7 @@
      *
      * @param device The remote device to send this response to
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void readPhy(BluetoothDevice device) {
         try {
             mService.serverReadPhy(mServerIf, device.getAddress());
@@ -645,14 +656,15 @@
      * <li>{@link BluetoothGattServerCallback#onDescriptorWriteRequest}
      * </ul>
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param device The remote device to send this response to
      * @param requestId The ID of the request that was received with the callback
      * @param status The status of the request to be sent to the remote devices
      * @param offset Value offset for partial read/write response
      * @param value The value of the attribute that was read/written (optional)
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean sendResponse(BluetoothDevice device, int requestId,
             int status, int offset, byte[] value) {
         if (VDBG) Log.d(TAG, "sendResponse() - device: " + device.getAddress());
@@ -677,8 +689,6 @@
      * for every client that requests notifications/indications by writing
      * to the "Client Configuration" descriptor for the given characteristic.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param device The remote device to receive the notification/indication
      * @param characteristic The local characteristic that has been updated
      * @param confirm true to request confirmation from the client (indication), false to send a
@@ -686,6 +696,9 @@
      * @return true, if the notification has been triggered successfully
      * @throws IllegalArgumentException
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean notifyCharacteristicChanged(BluetoothDevice device,
             BluetoothGattCharacteristic characteristic, boolean confirm) {
         if (VDBG) Log.d(TAG, "notifyCharacteristicChanged() - device: " + device.getAddress());
@@ -724,11 +737,12 @@
      * whether this service has been added successfully. Do not add another service
      * before this callback.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param service Service to be added to the list of services provided by this device.
      * @return true, if the request to add service has been initiated
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean addService(BluetoothGattService service) {
         if (DBG) Log.d(TAG, "addService() - service: " + service.getUuid());
         if (mService == null || mServerIf == 0) return false;
@@ -748,11 +762,12 @@
     /**
      * Removes a service from the list of services to be provided.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param service Service to be removed.
      * @return true, if the service has been removed
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean removeService(BluetoothGattService service) {
         if (DBG) Log.d(TAG, "removeService() - service: " + service.getUuid());
         if (mService == null || mServerIf == 0) return false;
@@ -774,8 +789,10 @@
 
     /**
      * Remove all services from the list of provided services.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void clearServices() {
         if (DBG) Log.d(TAG, "clearServices()");
         if (mService == null || mServerIf == 0) return;
@@ -794,10 +811,9 @@
      * <p>An application must call {@link #addService} to add a serice to the
      * list of services offered by this device.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @return List of services. Returns an empty list if no services have been added yet.
      */
+    @RequiresLegacyBluetoothPermission
     public List<BluetoothGattService> getServices() {
         return mServices;
     }
@@ -809,12 +825,11 @@
      * <p>If multiple instances of the same service (as identified by UUID)
      * exist, the first instance of the service is returned.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param uuid UUID of the requested service
      * @return BluetoothGattService if supported, or null if the requested service is not offered by
      * this device.
      */
+    @RequiresLegacyBluetoothPermission
     public BluetoothGattService getService(UUID uuid) {
         for (BluetoothGattService service : mServices) {
             if (service.getUuid().equals(uuid)) {
diff --git a/core/java/android/bluetooth/BluetoothGattService.java b/core/java/android/bluetooth/BluetoothGattService.java
index 23dc7c8..f64d09f 100644
--- a/core/java/android/bluetooth/BluetoothGattService.java
+++ b/core/java/android/bluetooth/BluetoothGattService.java
@@ -15,6 +15,9 @@
  */
 package android.bluetooth;
 
+import android.annotation.RequiresPermission;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
@@ -98,7 +101,6 @@
 
     /**
      * Create a new BluetoothGattService.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
      *
      * @param uuid The UUID for this service
      * @param serviceType The type of this service,
@@ -225,11 +227,11 @@
 
     /**
      * Add an included service to this service.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
      *
      * @param service The service to be added
      * @return true, if the included service was added to the service
      */
+    @RequiresLegacyBluetoothPermission
     public boolean addService(BluetoothGattService service) {
         mIncludedServices.add(service);
         return true;
@@ -237,11 +239,11 @@
 
     /**
      * Add a characteristic to this service.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
      *
      * @param characteristic The characteristics to be added
      * @return true, if the characteristic was added to the service
      */
+    @RequiresLegacyBluetoothPermission
     public boolean addCharacteristic(BluetoothGattCharacteristic characteristic) {
         mCharacteristics.add(characteristic);
         characteristic.setService(this);
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 632572d..84e8c51 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -22,6 +22,9 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
@@ -70,10 +73,10 @@
      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_CONNECTION_STATE_CHANGED =
             "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
@@ -90,10 +93,10 @@
      * </ul>
      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
      * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED},
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
-     * to receive.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_AUDIO_STATE_CHANGED =
             "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
@@ -107,11 +110,11 @@
      * be null if no device is active. </li>
      * </ul>
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
-     *
      * @hide
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     @UnsupportedAppUsage(trackingBug = 171933273)
     public static final String ACTION_ACTIVE_DEVICE_CHANGED =
@@ -147,9 +150,10 @@
      * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li>
      * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li>
      * </ul>
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
-     * to receive.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT =
             "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
@@ -299,10 +303,12 @@
      * are given an assigned number. Below shows the assigned number of Indicator added so far
      * - Enhanced Safety - 1, Valid Values: 0 - Disabled, 1 - Enabled
      * - Battery Level - 2, Valid Values: 0~100 - Remaining level of Battery
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to receive.
      *
      * @hide
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public static final String ACTION_HF_INDICATORS_VALUE_CHANGED =
             "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED";
 
@@ -432,15 +438,17 @@
      * the state. Users can get the connection state of the profile
      * from this intent.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
-     * permission.
-     *
      * @param device Remote Bluetooth Device
      * @return false on immediate error, true otherwise
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+    })
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
         final IBluetoothHeadset service = mService;
@@ -474,15 +482,14 @@
      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
      * two scenarios.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
-     * permission.
-     *
      * @param device Remote Bluetooth Device
      * @return false on immediate error, true otherwise
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothHeadset service = mService;
@@ -502,6 +509,7 @@
      * {@inheritDoc}
      */
     @Override
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
         final IBluetoothHeadset service = mService;
@@ -521,6 +529,7 @@
      * {@inheritDoc}
      */
     @Override
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
         final IBluetoothHeadset service = mService;
@@ -540,6 +549,7 @@
      * {@inheritDoc}
      */
     @Override
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getConnectionState(" + device + ")");
         final IBluetoothHeadset service = mService;
@@ -571,7 +581,12 @@
      */
     @Deprecated
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+    })
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
         final IBluetoothHeadset service = mService;
@@ -605,7 +620,11 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+    })
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -638,7 +657,9 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
         final IBluetoothHeadset service = mService;
@@ -688,7 +709,9 @@
      * @param device Bluetooth device
      * @return true if echo cancellation and/or noise reduction is supported, false otherwise
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean isNoiseReductionSupported(@NonNull BluetoothDevice device) {
         if (DBG) log("isNoiseReductionSupported()");
         final IBluetoothHeadset service = mService;
@@ -709,7 +732,9 @@
      * @param device Bluetooth device
      * @return true if voice recognition is supported, false otherwise
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean isVoiceRecognitionSupported(@NonNull BluetoothDevice device) {
         if (DBG) log("isVoiceRecognitionSupported()");
         final IBluetoothHeadset service = mService;
@@ -738,13 +763,17 @@
      * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
      * in case of failure to establish the audio connection.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param device Bluetooth headset
      * @return false if there is no headset connected, or the connected headset doesn't support
      * voice recognition, or voice recognition is already started, or audio channel is occupied,
      * or on error, true otherwise
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+    })
     public boolean startVoiceRecognition(BluetoothDevice device) {
         if (DBG) log("startVoiceRecognition()");
         final IBluetoothHeadset service = mService;
@@ -767,12 +796,13 @@
      * If this function returns true, this intent will be broadcasted with
      * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param device Bluetooth headset
      * @return false if there is no headset connected, or voice recognition has not started,
      * or voice recognition has ended on this headset, or on error, true otherwise
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean stopVoiceRecognition(BluetoothDevice device) {
         if (DBG) log("stopVoiceRecognition()");
         final IBluetoothHeadset service = mService;
@@ -790,11 +820,12 @@
     /**
      * Check if Bluetooth SCO audio is connected.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param device Bluetooth headset
      * @return true if SCO is connected, false otherwise or on error
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean isAudioConnected(BluetoothDevice device) {
         if (VDBG) log("isAudioConnected()");
         final IBluetoothHeadset service = mService;
@@ -827,6 +858,7 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getAudioState(BluetoothDevice device) {
         if (VDBG) log("getAudioState");
         final IBluetoothHeadset service = mService;
@@ -853,6 +885,7 @@
      * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void setAudioRouteAllowed(boolean allowed) {
         if (VDBG) log("setAudioRouteAllowed");
         final IBluetoothHeadset service = mService;
@@ -874,6 +907,7 @@
      *
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean getAudioRouteAllowed() {
         if (VDBG) log("getAudioRouteAllowed");
         final IBluetoothHeadset service = mService;
@@ -897,6 +931,7 @@
      * False to use SCO audio in normal manner
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void setForceScoAudio(boolean forced) {
         if (VDBG) log("setForceScoAudio " + String.valueOf(forced));
         final IBluetoothHeadset service = mService;
@@ -915,12 +950,13 @@
     /**
      * Check if at least one headset's SCO audio is connected or connecting
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @return true if at least one device's SCO audio is connected or connecting, false otherwise
      * or on error
      * @hide
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean isAudioOn() {
         if (VDBG) log("isAudioOn()");
         final IBluetoothHeadset service = mService;
@@ -955,6 +991,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean connectAudio() {
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled()) {
@@ -982,6 +1019,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disconnectAudio() {
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled()) {
@@ -1018,7 +1056,12 @@
      *  - binder is dead or Bluetooth is disabled or other error
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+    })
     @UnsupportedAppUsage
     public boolean startScoUsingVirtualVoiceCall() {
         if (DBG) log("startScoUsingVirtualVoiceCall()");
@@ -1048,7 +1091,12 @@
      *  - binder is dead or Bluetooth is disabled or other error
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+    })
     @UnsupportedAppUsage
     public boolean stopScoUsingVirtualVoiceCall() {
         if (DBG) log("stopScoUsingVirtualVoiceCall()");
@@ -1075,6 +1123,10 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+    })
     public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
             int type, String name) {
         final IBluetoothHeadset service = mService;
@@ -1095,6 +1147,10 @@
      *
      * @hide
      */
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+    })
     public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
             String number, int type) {
         final IBluetoothHeadset service = mService;
@@ -1119,8 +1175,6 @@
      *
      * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param device Bluetooth headset.
      * @param command A vendor-specific command.
      * @param arg The argument that will be attached to the command.
@@ -1128,6 +1182,9 @@
      * vendor-specific unsolicited result code, or on error. {@code true} otherwise.
      * @throws IllegalArgumentException if {@code command} is {@code null}.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
             String arg) {
         if (DBG) {
@@ -1164,15 +1221,17 @@
      * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
      * with the active device.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
-     * permission.
-     *
      * @param device Remote Bluetooth Device, could be null if phone call audio should not be
      * streamed to a headset
      * @return false on immediate error, true otherwise
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+    })
     @UnsupportedAppUsage(trackingBug = 171933273)
     public boolean setActiveDevice(@Nullable BluetoothDevice device) {
         if (DBG) {
@@ -1201,7 +1260,9 @@
      */
     @UnsupportedAppUsage(trackingBug = 171933273)
     @Nullable
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothDevice getActiveDevice() {
         if (VDBG) {
             Log.d(TAG, "getActiveDevice");
@@ -1227,7 +1288,9 @@
      * @return true if in-band ringing is enabled, false if in-band ringing is disabled
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean isInbandRingingEnabled() {
         if (DBG) {
             log("isInbandRingingEnabled()");
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java
index e5b2a1e..092130d 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClient.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java
@@ -19,6 +19,8 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.Binder;
@@ -447,6 +449,7 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
         final IBluetoothHeadsetClient service =
@@ -473,6 +476,7 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothHeadsetClient service =
@@ -495,6 +499,7 @@
      * @return list of connected devices; empty list if nothing is connected.
      */
     @Override
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
         final IBluetoothHeadsetClient service =
@@ -519,6 +524,7 @@
      * list if nothing matches the <code>states</code>
      */
     @Override
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
         final IBluetoothHeadsetClient service =
@@ -542,6 +548,7 @@
      * @return the state of connection of the device
      */
     @Override
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getConnectionState(" + device + ")");
         final IBluetoothHeadsetClient service =
@@ -569,7 +576,7 @@
      * @return true if priority is set, false on error
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
         return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
@@ -587,7 +594,7 @@
      * @return true if connectionPolicy is set, false on error
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -619,7 +626,9 @@
      * @return priority of the device
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
         return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
@@ -636,7 +645,9 @@
      * @return connection policy of the device
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothHeadsetClient service =
@@ -664,6 +675,7 @@
      * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature
      * is not supported.</p>
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean startVoiceRecognition(BluetoothDevice device) {
         if (DBG) log("startVoiceRecognition()");
         final IBluetoothHeadsetClient service =
@@ -688,6 +700,7 @@
      * @return <code>true</code> if command has been issued successfully; <code>false</code>
      * otherwise.
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId,
                                              String atCommand) {
         if (DBG) log("sendVendorSpecificCommand()");
@@ -715,6 +728,7 @@
      * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature
      * is not supported.</p>
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean stopVoiceRecognition(BluetoothDevice device) {
         if (DBG) log("stopVoiceRecognition()");
         final IBluetoothHeadsetClient service =
@@ -736,6 +750,7 @@
      * @param device remote device
      * @return list of calls; empty list if none call exists
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) {
         if (DBG) log("getCurrentCalls()");
         final IBluetoothHeadsetClient service =
@@ -757,6 +772,7 @@
      * @param device remote device
      * @return bundle of AG  indicators; null if device is not in CONNECTED state
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public Bundle getCurrentAgEvents(BluetoothDevice device) {
         if (DBG) log("getCurrentCalls()");
         final IBluetoothHeadsetClient service =
@@ -782,6 +798,7 @@
      * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean acceptCall(BluetoothDevice device, int flag) {
         if (DBG) log("acceptCall()");
         final IBluetoothHeadsetClient service =
@@ -804,6 +821,7 @@
      * @return <code>true</code> if command has been issued successfully; <code>false</code>
      * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean holdCall(BluetoothDevice device) {
         if (DBG) log("holdCall()");
         final IBluetoothHeadsetClient service =
@@ -831,6 +849,7 @@
      * supported.</p>
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean rejectCall(BluetoothDevice device) {
         if (DBG) log("rejectCall()");
         final IBluetoothHeadsetClient service =
@@ -862,6 +881,7 @@
      * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not
      * supported.</p>
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) {
         if (DBG) log("terminateCall()");
         final IBluetoothHeadsetClient service =
@@ -891,6 +911,7 @@
      * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not
      * supported.</p>
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean enterPrivateMode(BluetoothDevice device, int index) {
         if (DBG) log("enterPrivateMode()");
         final IBluetoothHeadsetClient service =
@@ -919,6 +940,7 @@
      * #EXTRA_AG_FEATURE_MERGE_AND_DETACH}. This method invocation will fail silently when feature
      * is not supported.</p>
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean explicitCallTransfer(BluetoothDevice device) {
         if (DBG) log("explicitCallTransfer()");
         final IBluetoothHeadsetClient service =
@@ -943,6 +965,7 @@
      * successfully; <code>{@link null}</code> otherwise; upon completion HFP sends {@link
      * #ACTION_CALL_CHANGED} intent in case of success; {@link #ACTION_RESULT} is sent otherwise;
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) {
         if (DBG) log("dial()");
         final IBluetoothHeadsetClient service =
@@ -968,6 +991,7 @@
      * @return <code>true</code> if command has been issued successfully; <code>false</code>
      * otherwise; upon completion HFP sends {@link #ACTION_RESULT} intent;
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean sendDTMF(BluetoothDevice device, byte code) {
         if (DBG) log("sendDTMF()");
         final IBluetoothHeadsetClient service =
@@ -1089,6 +1113,7 @@
      * @return <code>true</code> if command has been issued successfully; <code>false</code>
      * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent;
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean connectAudio(BluetoothDevice device) {
         final IBluetoothHeadsetClient service =
                 getService();
@@ -1114,6 +1139,7 @@
      * @return <code>true</code> if command has been issued successfully; <code>false</code>
      * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent;
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disconnectAudio(BluetoothDevice device) {
         final IBluetoothHeadsetClient service =
                 getService();
@@ -1136,6 +1162,7 @@
      * @param device remote device
      * @return bundle of AG features; null if no service or AG not connected
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public Bundle getCurrentAgFeatures(BluetoothDevice device) {
         final IBluetoothHeadsetClient service =
                 getService();
diff --git a/core/java/android/bluetooth/BluetoothHealth.java b/core/java/android/bluetooth/BluetoothHealth.java
index 5fd60e0..65f68a9 100644
--- a/core/java/android/bluetooth/BluetoothHealth.java
+++ b/core/java/android/bluetooth/BluetoothHealth.java
@@ -16,6 +16,10 @@
 
 package android.bluetooth;
 
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
 
@@ -111,8 +115,6 @@
      * which will act as the {@link #SOURCE_ROLE}. This is an asynchronous call and so
      * the callback is used to notify success or failure if the function returns true.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param name The friendly name associated with the application or configuration.
      * @param dataType The dataType of the Source role of Health Profile to which the sink wants to
      * connect to.
@@ -126,6 +128,10 @@
      * {@link BluetoothDevice#createL2capChannel(int)}
      */
     @Deprecated
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public boolean registerSinkAppConfiguration(String name, int dataType,
             BluetoothHealthCallback callback) {
         Log.e(TAG, "registerSinkAppConfiguration(): BluetoothHealth is deprecated");
@@ -136,8 +142,6 @@
      * Unregister an application configuration that has been registered using
      * {@link #registerSinkAppConfiguration}
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param config The health app configuration
      * @return Success or failure.
      *
@@ -147,6 +151,10 @@
      * {@link BluetoothDevice#createL2capChannel(int)}
      */
     @Deprecated
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
         Log.e(TAG, "unregisterAppConfiguration(): BluetoothHealth is deprecated");
         return false;
@@ -157,8 +165,6 @@
      * This is an asynchronous call. If this function returns true, the callback
      * associated with the application configuration will be called.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param device The remote Bluetooth device.
      * @param config The application configuration which has been registered using {@link
      * #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
@@ -170,6 +176,10 @@
      * {@link BluetoothDevice#createL2capChannel(int)}
      */
     @Deprecated
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public boolean connectChannelToSource(BluetoothDevice device,
             BluetoothHealthAppConfiguration config) {
         Log.e(TAG, "connectChannelToSource(): BluetoothHealth is deprecated");
@@ -181,8 +191,6 @@
      * This is an asynchronous call. If this function returns true, the callback
      * associated with the application configuration will be called.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * @param device The remote Bluetooth device.
      * @param config The application configuration which has been registered using {@link
      * #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
@@ -195,6 +203,10 @@
      * {@link BluetoothDevice#createL2capChannel(int)}
      */
     @Deprecated
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public boolean disconnectChannel(BluetoothDevice device,
             BluetoothHealthAppConfiguration config, int channelId) {
         Log.e(TAG, "disconnectChannel(): BluetoothHealth is deprecated");
@@ -205,8 +217,6 @@
      * Get the file descriptor of the main channel associated with the remote device
      * and application configuration.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * <p> Its the responsibility of the caller to close the ParcelFileDescriptor
      * when done.
      *
@@ -220,6 +230,10 @@
      * {@link BluetoothDevice#createL2capChannel(int)}
      */
     @Deprecated
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
             BluetoothHealthAppConfiguration config) {
         Log.e(TAG, "getMainChannelFd(): BluetoothHealth is deprecated");
@@ -229,8 +243,6 @@
     /**
      * Get the current connection state of the profile.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * This is not specific to any application configuration but represents the connection
      * state of the local Bluetooth adapter with the remote device. This can be used
      * by applications like status bar which would just like to know the state of the
@@ -241,6 +253,10 @@
      * #STATE_CONNECTING}, {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
      */
     @Override
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public int getConnectionState(BluetoothDevice device) {
         Log.e(TAG, "getConnectionState(): BluetoothHealth is deprecated");
         return STATE_DISCONNECTED;
@@ -251,8 +267,6 @@
      *
      * <p> Return the set of devices which are in state {@link #STATE_CONNECTED}
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
      * This is not specific to any application configuration but represents the connection
      * state of the local Bluetooth adapter for this profile. This can be used
      * by applications like status bar which would just like to know the state of the
@@ -261,6 +275,10 @@
      * @return List of devices. The list will be empty on error.
      */
     @Override
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public List<BluetoothDevice> getConnectedDevices() {
         Log.e(TAG, "getConnectedDevices(): BluetoothHealth is deprecated");
         return new ArrayList<>();
@@ -273,8 +291,7 @@
      * <p> If none of the devices match any of the given states,
      * an empty list will be returned.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     * This is not specific to any application configuration but represents the connection
+     * <p>This is not specific to any application configuration but represents the connection
      * state of the local Bluetooth adapter for this profile. This can be used
      * by applications like status bar which would just like to know the state of the
      * local adapter.
@@ -284,6 +301,10 @@
      * @return List of devices. The list will be empty on error.
      */
     @Override
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         Log.e(TAG, "getDevicesMatchingConnectionStates(): BluetoothHealth is deprecated");
         return new ArrayList<>();
diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java
index ff78825e..8ceeff5 100644
--- a/core/java/android/bluetooth/BluetoothHearingAid.java
+++ b/core/java/android/bluetooth/BluetoothHearingAid.java
@@ -22,6 +22,9 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -64,10 +67,10 @@
      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_CONNECTION_STATE_CHANGED =
             "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED";
@@ -81,11 +84,11 @@
      * be null if no device is active. </li>
      * </ul>
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
-     *
      * @hide
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_ACTIVE_DEVICE_CHANGED =
@@ -167,7 +170,10 @@
      * @return false on immediate error, true otherwise
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
         final IBluetoothHearingAid service = getService();
@@ -225,6 +231,7 @@
      * {@inheritDoc}
      */
     @Override
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
         final IBluetoothHearingAid service = getService();
@@ -244,6 +251,7 @@
      * {@inheritDoc}
      */
     @Override
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
     @NonNull int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
@@ -264,6 +272,7 @@
      * {@inheritDoc}
      */
     @Override
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @BluetoothProfile.BtProfileState int getConnectionState(
     @NonNull BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
@@ -295,14 +304,14 @@
      * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
      * with the active device.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
-     * permission.
-     *
      * @param device the remote Bluetooth device. Could be null to clear
      * the active device and stop streaming audio to a Bluetooth device.
      * @return false on immediate error, true otherwise
      * @hide
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public boolean setActiveDevice(@Nullable BluetoothDevice device) {
         if (DBG) log("setActiveDevice(" + device + ")");
@@ -330,7 +339,9 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @NonNull List<BluetoothDevice> getActiveDevices() {
         if (VDBG) log("getActiveDevices()");
         final IBluetoothHearingAid service = getService();
@@ -357,7 +368,10 @@
      * @return true if priority is set, false on error
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
         return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
@@ -376,7 +390,10 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -531,7 +548,7 @@
      * @return SIDE_LEFT or SIDE_RIGHT
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
     public int getDeviceSide(BluetoothDevice device) {
         if (VDBG) {
             log("getDeviceSide(" + device + ")");
@@ -557,7 +574,7 @@
      * @return MODE_MONAURAL or MODE_BINAURAL
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
     public int getDeviceMode(BluetoothDevice device) {
         if (VDBG) {
             log("getDeviceMode(" + device + ")");
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
index 2baa738..c214d2b 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -21,6 +21,8 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.os.Binder;
@@ -56,9 +58,10 @@
      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link
      * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link
      * #STATE_DISCONNECTING}.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to receive.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_CONNECTION_STATE_CHANGED =
             "android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED";
@@ -436,6 +439,7 @@
 
     /** {@inheritDoc} */
     @Override
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getConnectedDevices() {
         final IBluetoothHidDevice service = getService();
         if (service != null) {
@@ -453,6 +457,7 @@
 
     /** {@inheritDoc} */
     @Override
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         final IBluetoothHidDevice service = getService();
         if (service != null) {
@@ -470,6 +475,7 @@
 
     /** {@inheritDoc} */
     @Override
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public int getConnectionState(BluetoothDevice device) {
         final IBluetoothHidDevice service = getService();
         if (service != null) {
@@ -508,6 +514,7 @@
      *     object is required.
      * @return true if the command is successfully sent; otherwise false.
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean registerApp(
             BluetoothHidDeviceAppSdpSettings sdp,
             BluetoothHidDeviceAppQosSettings inQos,
@@ -553,6 +560,7 @@
      *
      * @return true if the command is successfully sent; otherwise false.
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean unregisterApp() {
         boolean result = false;
 
@@ -578,6 +586,7 @@
      * @param data Report data, not including Report Id.
      * @return true if the command is successfully sent; otherwise false.
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
         boolean result = false;
 
@@ -604,6 +613,7 @@
      * @param data Report data, not including Report Id.
      * @return true if the command is successfully sent; otherwise false.
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
         boolean result = false;
 
@@ -628,6 +638,7 @@
      * @param error Error to be sent for SET_REPORT via HANDSHAKE.
      * @return true if the command is successfully sent; otherwise false.
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean reportError(BluetoothDevice device, byte error) {
         boolean result = false;
 
@@ -651,6 +662,7 @@
      * @return the current user name, or empty string if cannot get the name
      * {@hide}
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public String getUserAppName() {
         final IBluetoothHidDevice service = getService();
 
@@ -675,6 +687,7 @@
      *
      * @return true if the command is successfully sent; otherwise false.
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean connect(BluetoothDevice device) {
         boolean result = false;
 
@@ -699,6 +712,7 @@
      *
      * @return true if the command is successfully sent; otherwise false.
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disconnect(BluetoothDevice device) {
         boolean result = false;
 
@@ -734,7 +748,10 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
diff --git a/core/java/android/bluetooth/BluetoothHidHost.java b/core/java/android/bluetooth/BluetoothHidHost.java
index 9561d93..70e3809 100644
--- a/core/java/android/bluetooth/BluetoothHidHost.java
+++ b/core/java/android/bluetooth/BluetoothHidHost.java
@@ -21,6 +21,9 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.content.Context;
@@ -65,11 +68,11 @@
      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
      */
     @SuppressLint("ActionValue")
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_CONNECTION_STATE_CHANGED =
             "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
@@ -328,7 +331,7 @@
      */
     @SystemApi
     @Override
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
         final IBluetoothHidHost service = getService();
@@ -350,6 +353,7 @@
      * @hide
      */
     @Override
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
         final IBluetoothHidHost service = getService();
@@ -372,7 +376,7 @@
      */
     @SystemApi
     @Override
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public int getConnectionState(@NonNull BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
         if (device == null) {
@@ -503,12 +507,13 @@
     /**
      * Initiate virtual unplug for a HID input device.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
-     *
      * @param device Remote Bluetooth Device
      * @return false on immediate error, true otherwise
      * @hide
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean virtualUnplug(BluetoothDevice device) {
         if (DBG) log("virtualUnplug(" + device + ")");
         final IBluetoothHidHost service = getService();
@@ -529,12 +534,13 @@
     /**
      * Send Get_Protocol_Mode command to the connected HID input device.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
-     *
      * @param device Remote Bluetooth Device
      * @return false on immediate error, true otherwise
      * @hide
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean getProtocolMode(BluetoothDevice device) {
         if (VDBG) log("getProtocolMode(" + device + ")");
         final IBluetoothHidHost service = getService();
@@ -553,12 +559,13 @@
     /**
      * Send Set_Protocol_Mode command to the connected HID input device.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
-     *
      * @param device Remote Bluetooth Device
      * @return false on immediate error, true otherwise
      * @hide
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
         if (DBG) log("setProtocolMode(" + device + ")");
         final IBluetoothHidHost service = getService();
@@ -577,8 +584,6 @@
     /**
      * Send Get_Report command to the connected HID input device.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
-     *
      * @param device Remote Bluetooth Device
      * @param reportType Report type
      * @param reportId Report ID
@@ -586,6 +591,9 @@
      * @return false on immediate error, true otherwise
      * @hide
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean getReport(BluetoothDevice device, byte reportType, byte reportId,
             int bufferSize) {
         if (VDBG) {
@@ -608,14 +616,15 @@
     /**
      * Send Set_Report command to the connected HID input device.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
-     *
      * @param device Remote Bluetooth Device
      * @param reportType Report type
      * @param report Report receiving buffer size
      * @return false on immediate error, true otherwise
      * @hide
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean setReport(BluetoothDevice device, byte reportType, String report) {
         if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report);
         final IBluetoothHidHost service = getService();
@@ -634,13 +643,14 @@
     /**
      * Send Send_Data command to the connected HID input device.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
-     *
      * @param device Remote Bluetooth Device
      * @param report Report to send
      * @return false on immediate error, true otherwise
      * @hide
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean sendData(BluetoothDevice device, String report) {
         if (DBG) log("sendData(" + device + "), report=" + report);
         final IBluetoothHidHost service = getService();
@@ -659,12 +669,13 @@
     /**
      * Send Get_Idle_Time command to the connected HID input device.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
-     *
      * @param device Remote Bluetooth Device
      * @return false on immediate error, true otherwise
      * @hide
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean getIdleTime(BluetoothDevice device) {
         if (DBG) log("getIdletime(" + device + ")");
         final IBluetoothHidHost service = getService();
@@ -683,13 +694,14 @@
     /**
      * Send Set_Idle_Time command to the connected HID input device.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
-     *
      * @param device Remote Bluetooth Device
      * @param idleTime Idle time to be set on HID Device
      * @return false on immediate error, true otherwise
      * @hide
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
         if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime);
         final IBluetoothHidHost service = getService();
diff --git a/core/java/android/bluetooth/BluetoothLeAudio.java b/core/java/android/bluetooth/BluetoothLeAudio.java
index 3f00fa6..4f095f6 100644
--- a/core/java/android/bluetooth/BluetoothLeAudio.java
+++ b/core/java/android/bluetooth/BluetoothLeAudio.java
@@ -23,6 +23,9 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.content.Context;
 import android.os.Binder;
 import android.os.IBinder;
@@ -65,10 +68,10 @@
      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED =
             "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED";
@@ -82,11 +85,11 @@
      * be null if no device is active. </li>
      * </ul>
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
-     *
      * @hide
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED =
             "android.bluetooth.action.LE_AUDIO_ACTIVE_DEVICE_CHANGED";
@@ -122,7 +125,6 @@
     /**
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
     public void close() {
         mProfileConnector.disconnect();
     }
@@ -131,7 +133,6 @@
         return mProfileConnector.getService();
     }
 
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
     protected void finalize() {
         if (mCloseGuard != null) {
             mCloseGuard.warnIfOpen();
@@ -154,7 +155,7 @@
      * @return false on immediate error, true otherwise
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean connect(@Nullable BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
         try {
@@ -193,7 +194,7 @@
      * @return false on immediate error, true otherwise
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disconnect(@Nullable BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         try {
@@ -213,6 +214,7 @@
      * {@inheritDoc}
      */
     @Override
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
         try {
@@ -232,6 +234,7 @@
      * {@inheritDoc}
      */
     @Override
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
             @NonNull int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
@@ -252,7 +255,9 @@
      * {@inheritDoc}
      */
     @Override
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
         try {
@@ -289,7 +294,7 @@
      * @return false on immediate error, true otherwise
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean setActiveDevice(@Nullable BluetoothDevice device) {
         if (DBG) log("setActiveDevice(" + device + ")");
         try {
@@ -314,7 +319,7 @@
      * @hide
      */
     @NonNull
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
     public List<BluetoothDevice> getActiveDevices() {
         if (VDBG) log("getActiveDevices()");
         try {
@@ -337,7 +342,7 @@
      * @return group id that this device currently belongs to
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
     public int getGroupId(@NonNull BluetoothDevice device) {
         if (VDBG) log("getGroupId()");
         try {
@@ -365,7 +370,10 @@
      * @return true if connectionPolicy is set, false on error
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -398,7 +406,7 @@
      * @return connection policy of the device
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         try {
diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java
index d5c1c3e..a1e1b63 100644
--- a/core/java/android/bluetooth/BluetoothManager.java
+++ b/core/java/android/bluetooth/BluetoothManager.java
@@ -20,6 +20,8 @@
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.IBinder;
@@ -109,7 +111,9 @@
      * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_DISCONNECTED},
      * {@link BluetoothProfile#STATE_DISCONNECTING}
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getConnectionState(BluetoothDevice device, int profile) {
         if (DBG) Log.d(TAG, "getConnectionState()");
 
@@ -136,7 +140,9 @@
      * @param profile GATT or GATT_SERVER
      * @return List of devices. The list will be empty on error.
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getConnectedDevices(int profile) {
         if (DBG) Log.d(TAG, "getConnectedDevices");
         if (profile != BluetoothProfile.GATT && profile != BluetoothProfile.GATT_SERVER) {
@@ -177,7 +183,9 @@
      * {@link BluetoothProfile#STATE_DISCONNECTING},
      * @return List of devices. The list will be empty on error.
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int profile, int[] states) {
         if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates");
 
@@ -210,6 +218,7 @@
      * @param callback GATT server callback handler that will receive asynchronous callbacks.
      * @return BluetoothGattServer instance
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothGattServer openGattServer(Context context,
             BluetoothGattServerCallback callback) {
 
@@ -229,6 +238,7 @@
      * @return BluetoothGattServer instance
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothGattServer openGattServer(Context context,
             BluetoothGattServerCallback callback, boolean eatt_support) {
         return (openGattServer(context, callback, BluetoothDevice.TRANSPORT_AUTO, eatt_support));
@@ -249,6 +259,7 @@
      * @return BluetoothGattServer instance
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothGattServer openGattServer(Context context,
             BluetoothGattServerCallback callback, int transport) {
         return (openGattServer(context, callback, transport, false));
@@ -270,6 +281,7 @@
      * @return BluetoothGattServer instance
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothGattServer openGattServer(Context context,
             BluetoothGattServerCallback callback, int transport, boolean eatt_support) {
         if (context == null || callback == null) {
diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java
index 3554995..3e7b75a 100644
--- a/core/java/android/bluetooth/BluetoothMap.java
+++ b/core/java/android/bluetooth/BluetoothMap.java
@@ -93,7 +93,6 @@
         mCloseGuard.open("close");
     }
 
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
     protected void finalize() {
         if (mCloseGuard != null) {
             mCloseGuard.warnIfOpen();
@@ -110,7 +109,6 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
     public void close() {
         if (VDBG) log("close()");
         mProfileConnector.disconnect();
@@ -128,6 +126,7 @@
      *
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getState() {
         if (VDBG) log("getState()");
         final IBluetoothMap service = getService();
@@ -152,6 +151,7 @@
      *
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothDevice getClient() {
         if (VDBG) log("getClient()");
         final IBluetoothMap service = getService();
@@ -175,6 +175,7 @@
      *
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean isConnected(BluetoothDevice device) {
         if (VDBG) log("isConnected(" + device + ")");
         final IBluetoothMap service = getService();
@@ -211,6 +212,7 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothMap service = getService();
@@ -257,7 +259,10 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (DBG) log("getConnectedDevices()");
         final IBluetoothMap service = getService();
@@ -280,6 +285,7 @@
      *
      * @hide
      */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) log("getDevicesMatchingStates()");
         final IBluetoothMap service = getService();
@@ -302,6 +308,7 @@
      *
      * @hide
      */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public int getConnectionState(BluetoothDevice device) {
         if (DBG) log("getConnectionState(" + device + ")");
         final IBluetoothMap service = getService();
@@ -328,7 +335,10 @@
      * @return true if priority is set, false on error
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
         return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
@@ -347,7 +357,10 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -378,7 +391,10 @@
      * @return priority of the device
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
         return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
@@ -396,7 +412,10 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothMap service = getService();
diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java
index 0312a21..db74a90 100644
--- a/core/java/android/bluetooth/BluetoothMapClient.java
+++ b/core/java/android/bluetooth/BluetoothMapClient.java
@@ -192,6 +192,7 @@
      * currently connected to the Map service.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean isConnected(BluetoothDevice device) {
         if (VDBG) Log.d(TAG, "isConnected(" + device + ")");
         final IBluetoothMapClient service = getService();
@@ -214,7 +215,10 @@
      *
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean connect(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE");
         final IBluetoothMapClient service = getService();
@@ -239,7 +243,10 @@
      *
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "disconnect(" + device + ")");
         final IBluetoothMapClient service = getService();
@@ -261,6 +268,7 @@
      * @hide
      */
     @Override
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getConnectedDevices() {
         if (DBG) Log.d(TAG, "getConnectedDevices()");
         final IBluetoothMapClient service = getService();
@@ -283,6 +291,7 @@
      * @hide
      */
     @Override
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) Log.d(TAG, "getDevicesMatchingStates()");
         final IBluetoothMapClient service = getService();
@@ -305,6 +314,7 @@
      * @hide
      */
     @Override
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public int getConnectionState(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "getConnectionState(" + device + ")");
         final IBluetoothMapClient service = getService();
@@ -331,7 +341,10 @@
      * @return true if priority is set, false on error
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) Log.d(TAG, "setPriority(" + device + ", " + priority + ")");
         return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
@@ -349,7 +362,10 @@
      * @return true if connectionPolicy is set, false on error
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) Log.d(TAG, "setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -380,7 +396,10 @@
      * @return priority of the device
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public int getPriority(BluetoothDevice device) {
         if (VDBG) Log.d(TAG, "getPriority(" + device + ")");
         return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
@@ -397,7 +416,10 @@
      * @return connection policy of the device
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) Log.d(TAG, "getConnectionPolicy(" + device + ")");
         final IBluetoothMapClient service = getService();
@@ -427,7 +449,10 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.SEND_SMS)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.SEND_SMS,
+    })
     public boolean sendMessage(@NonNull BluetoothDevice device, @NonNull Collection<Uri> contacts,
             @NonNull String message, @Nullable PendingIntent sentIntent,
             @Nullable PendingIntent deliveredIntent) {
@@ -459,6 +484,10 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.SEND_SMS,
+    })
     public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
             PendingIntent sentIntent, PendingIntent deliveredIntent) {
         if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message);
@@ -481,6 +510,10 @@
      * @return true if the message is enqueued, false on error
      * @hide
      */
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.READ_SMS,
+    })
     public boolean getUnreadMessages(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")");
         final IBluetoothMapClient service = getService();
@@ -503,6 +536,7 @@
      *         MapSupportedFeatures field is set. False is returned otherwise.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean isUploadingSupported(BluetoothDevice device) {
         final IBluetoothMapClient service = getService();
         try {
@@ -530,7 +564,10 @@
      * @return <code>true</code> if request has been sent, <code>false</code> on error
      * @hide
      */
-    @RequiresPermission(Manifest.permission.READ_SMS)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.READ_SMS,
+    })
     public boolean setMessageStatus(BluetoothDevice device, String handle, int status) {
         if (DBG) Log.d(TAG, "setMessageStatus(" + device + ", " + handle + ", " + status + ")");
         final IBluetoothMapClient service = getService();
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index ecd718c..b3924b1f 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -22,6 +22,8 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -74,10 +76,11 @@
      *
      * <p> {@link #EXTRA_LOCAL_ROLE} can be one of {@link #LOCAL_NAP_ROLE} or
      * {@link #LOCAL_PANU_ROLE}
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
      */
     @SuppressLint("ActionValue")
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_CONNECTION_STATE_CHANGED =
             "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED";
@@ -102,9 +105,10 @@
      *
      * <p> {@link #EXTRA_TETHERING_STATE} can be any of {@link #TETHERING_STATE_OFF} or
      * {@link #TETHERING_STATE_ON}
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_TETHERING_STATE_CHANGED =
             "android.bluetooth.action.TETHERING_STATE_CHANGED";
@@ -236,6 +240,10 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
         final IBluetoothPan service = getService();
@@ -274,6 +282,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothPan service = getService();
@@ -302,7 +311,10 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -330,7 +342,10 @@
      */
     @SystemApi
     @Override
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
         final IBluetoothPan service = getService();
@@ -351,7 +366,12 @@
      * @hide
      */
     @Override
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
         final IBluetoothPan service = getService();
@@ -396,7 +416,11 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+            android.Manifest.permission.TETHER_PRIVILEGED,
+    })
     public void setBluetoothTethering(boolean value) {
         String pkgName = mContext.getOpPackageName();
         if (DBG) log("setBluetoothTethering(" + value + "), calling package:" + pkgName);
@@ -417,7 +441,7 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean isTetheringOn() {
         if (VDBG) log("isTetheringOn()");
         final IBluetoothPan service = getService();
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index 6e5c45f..6c2e5bf 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -22,6 +22,8 @@
 import android.annotation.SdkConstant;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
@@ -82,8 +84,6 @@
      *  can be any of {@link BluetoothProfile#STATE_DISCONNECTED},
      *  {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED},
      *  {@link BluetoothProfile#STATE_DISCONNECTING}.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
      *
      * @hide
      */
@@ -142,6 +142,7 @@
         doBind();
     }
 
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     boolean doBind() {
         synchronized (mConnection) {
             try {
@@ -216,6 +217,7 @@
      * @hide
      */
     @Override
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getConnectedDevices() {
         log("getConnectedDevices()");
         final IBluetoothPbap service = mService;
@@ -262,6 +264,7 @@
      * @hide
      */
     @Override
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         log("getDevicesMatchingConnectionStates: states=" + Arrays.toString(states));
         final IBluetoothPbap service = mService;
@@ -294,7 +297,10 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -324,6 +330,7 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disconnect(BluetoothDevice device) {
         log("disconnect()");
         final IBluetoothPbap service = mService;
diff --git a/core/java/android/bluetooth/BluetoothPbapClient.java b/core/java/android/bluetooth/BluetoothPbapClient.java
index f356da1..2c8fbc2 100644
--- a/core/java/android/bluetooth/BluetoothPbapClient.java
+++ b/core/java/android/bluetooth/BluetoothPbapClient.java
@@ -19,6 +19,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.os.Binder;
 import android.os.IBinder;
@@ -160,6 +161,7 @@
      * @return list of connected devices
      */
     @Override
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getConnectedDevices() {
         if (DBG) {
             log("getConnectedDevices()");
@@ -185,6 +187,7 @@
      * @return list of matching devices
      */
     @Override
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) {
             log("getDevicesMatchingStates()");
@@ -210,6 +213,7 @@
      * @return device connection state
      */
     @Override
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public int getConnectionState(BluetoothDevice device) {
         if (DBG) {
             log("getConnectionState(" + device + ")");
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 201d6c4..70053ee 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -14,12 +14,9 @@
  * limitations under the License.
  */
 
-
 package android.bluetooth;
 
-import android.Manifest;
 import android.annotation.IntDef;
-import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -300,7 +297,6 @@
      *
      * @return List of devices. The list will be empty on error.
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
     public List<BluetoothDevice> getConnectedDevices();
 
     /**
@@ -314,7 +310,6 @@
      * #STATE_CONNECTING}, {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING},
      * @return List of devices. The list will be empty on error.
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states);
 
     /**
@@ -324,7 +319,6 @@
      * @return State of the profile connection. One of {@link #STATE_CONNECTED}, {@link
      * #STATE_CONNECTING}, {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH)
     @BtProfileState int getConnectionState(BluetoothDevice device);
 
     /**
diff --git a/core/java/android/bluetooth/BluetoothProfileConnector.java b/core/java/android/bluetooth/BluetoothProfileConnector.java
index 863fd36..12abcc4 100644
--- a/core/java/android/bluetooth/BluetoothProfileConnector.java
+++ b/core/java/android/bluetooth/BluetoothProfileConnector.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -78,6 +80,7 @@
         mServiceName = serviceName;
     }
 
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     private boolean doBind() {
         synchronized (mConnection) {
             if (mService == null) {
diff --git a/core/java/android/bluetooth/BluetoothSap.java b/core/java/android/bluetooth/BluetoothSap.java
index 0d70dbd..c85494c 100644
--- a/core/java/android/bluetooth/BluetoothSap.java
+++ b/core/java/android/bluetooth/BluetoothSap.java
@@ -18,6 +18,9 @@
 
 import android.Manifest;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.Binder;
@@ -61,11 +64,11 @@
      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
-     *
      * @hide
      */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public static final String ACTION_CONNECTION_STATE_CHANGED =
             "android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED";
 
@@ -140,6 +143,7 @@
      * connected to the Sap service.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getState() {
         if (VDBG) log("getState()");
         final IBluetoothSap service = getService();
@@ -163,6 +167,7 @@
      * this proxy object is not connected to the Sap service.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothDevice getClient() {
         if (VDBG) log("getClient()");
         final IBluetoothSap service = getService();
@@ -186,6 +191,7 @@
      *
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean isConnected(BluetoothDevice device) {
         if (VDBG) log("isConnected(" + device + ")");
         final IBluetoothSap service = getService();
@@ -221,6 +227,7 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothSap service = getService();
@@ -242,6 +249,7 @@
      * @return list of connected devices
      * @hide
      */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getConnectedDevices() {
         if (DBG) log("getConnectedDevices()");
         final IBluetoothSap service = getService();
@@ -263,6 +271,7 @@
      * @return list of matching devices
      * @hide
      */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) log("getDevicesMatchingStates()");
         final IBluetoothSap service = getService();
@@ -284,6 +293,7 @@
      * @return device connection state
      * @hide
      */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public int getConnectionState(BluetoothDevice device) {
         if (DBG) log("getConnectionState(" + device + ")");
         final IBluetoothSap service = getService();
@@ -310,7 +320,10 @@
      * @return true if priority is set, false on error
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
         return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
@@ -328,7 +341,10 @@
      * @return true if connectionPolicy is set, false on error
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean setConnectionPolicy(BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -359,7 +375,10 @@
      * @return priority of the device
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
         return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
@@ -376,7 +395,10 @@
      * @return connection policy of the device
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public @ConnectionPolicy int getConnectionPolicy(BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothSap service = getService();
diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java
index 5c1bcaf..5082235 100644
--- a/core/java/android/bluetooth/BluetoothServerSocket.java
+++ b/core/java/android/bluetooth/BluetoothServerSocket.java
@@ -62,9 +62,6 @@
  * safe. In particular, {@link #close} will always immediately abort ongoing
  * operations and close the server socket.
  *
- * <p class="note"><strong>Note:</strong>
- * Requires the {@link android.Manifest.permission#BLUETOOTH} permission.
- *
  * <div class="special reference">
  * <h3>Developer Guides</h3>
  * <p>For more information about using Bluetooth, read the
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index 65381db..ef88147 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.net.LocalSocket;
 import android.os.Build;
@@ -70,9 +72,6 @@
  * safe. In particular, {@link #close} will always immediately abort ongoing
  * operations and close the socket.
  *
- * <p class="note"><strong>Note:</strong>
- * Requires the {@link android.Manifest.permission#BLUETOOTH} permission.
- *
  * <div class="special reference">
  * <h3>Developer Guides</h3>
  * <p>For more information about using Bluetooth, read the
@@ -199,6 +198,7 @@
      * @throws IOException On error, for example Bluetooth not available, or insufficient
      * privileges
      */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     /*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt,
             BluetoothDevice device, int port, ParcelUuid uuid, boolean mitm, boolean min16DigitPin)
             throws IOException {
@@ -386,6 +386,7 @@
      *
      * @throws IOException on error, for example connection failure
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void connect() throws IOException {
         if (mDevice == null) throw new IOException("Connect is called on null device");
 
@@ -427,6 +428,7 @@
      * Currently returns unix errno instead of throwing IOException,
      * so that BluetoothAdapter can check the error code for EADDRINUSE
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     /*package*/ int bindListen() {
         int ret;
         if (mSocketState == SocketState.CLOSED) return EBADFD;
@@ -682,6 +684,7 @@
      * connection. This function is currently used for testing only.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void requestMaximumTxDataLength() throws IOException {
         if (mDevice == null) {
             throw new IOException("requestMaximumTxDataLength is called on null device");
diff --git a/core/java/android/bluetooth/annotations/RequiresBluetoothAdvertisePermission.java b/core/java/android/bluetooth/annotations/RequiresBluetoothAdvertisePermission.java
new file mode 100644
index 0000000..c508c2c
--- /dev/null
+++ b/core/java/android/bluetooth/annotations/RequiresBluetoothAdvertisePermission.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.Manifest;
+import android.os.Build;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc For apps targeting {@link Build.VERSION_CODES#S} or or higher,
+ *            this requires the {@link Manifest.permission#BLUETOOTH_ADVERTISE}
+ *            permission which can be gained with
+ *            {@link android.app.Activity#requestPermissions(String[], int)}.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, FIELD})
+public @interface RequiresBluetoothAdvertisePermission {
+}
diff --git a/core/java/android/bluetooth/annotations/RequiresBluetoothConnectPermission.java b/core/java/android/bluetooth/annotations/RequiresBluetoothConnectPermission.java
new file mode 100644
index 0000000..e159eaa
--- /dev/null
+++ b/core/java/android/bluetooth/annotations/RequiresBluetoothConnectPermission.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.Manifest;
+import android.os.Build;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc For apps targeting {@link Build.VERSION_CODES#S} or or higher,
+ *            this requires the {@link Manifest.permission#BLUETOOTH_CONNECT}
+ *            permission which can be gained with
+ *            {@link android.app.Activity#requestPermissions(String[], int)}.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, FIELD})
+public @interface RequiresBluetoothConnectPermission {
+}
diff --git a/core/java/android/bluetooth/annotations/RequiresBluetoothLocationPermission.java b/core/java/android/bluetooth/annotations/RequiresBluetoothLocationPermission.java
new file mode 100644
index 0000000..2bb3204
--- /dev/null
+++ b/core/java/android/bluetooth/annotations/RequiresBluetoothLocationPermission.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.Manifest;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc In addition, this requires either the
+ *            {@link Manifest.permission#ACCESS_FINE_LOCATION}
+ *            permission or a strong assertion that you will never derive the
+ *            physical location of the device. You can make this assertion by
+ *            declaring {@code usesPermissionFlags="neverForLocation"} on the
+ *            relevant {@code <uses-permission>} manifest tag, but it may
+ *            restrict the types of Bluetooth devices you can interact with.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, FIELD})
+public @interface RequiresBluetoothLocationPermission {
+}
diff --git a/core/java/android/bluetooth/annotations/RequiresBluetoothScanPermission.java b/core/java/android/bluetooth/annotations/RequiresBluetoothScanPermission.java
new file mode 100644
index 0000000..800ff39
--- /dev/null
+++ b/core/java/android/bluetooth/annotations/RequiresBluetoothScanPermission.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.Manifest;
+import android.os.Build;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc For apps targeting {@link Build.VERSION_CODES#S} or or higher,
+ *            this requires the {@link Manifest.permission#BLUETOOTH_SCAN}
+ *            permission which can be gained with
+ *            {@link android.app.Activity#requestPermissions(String[], int)}.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, FIELD})
+public @interface RequiresBluetoothScanPermission {
+}
diff --git a/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothAdminPermission.java b/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothAdminPermission.java
new file mode 100644
index 0000000..9adf695
--- /dev/null
+++ b/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothAdminPermission.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.Manifest;
+import android.os.Build;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc For apps targeting {@link Build.VERSION_CODES#R} or lower, this
+ *            requires the {@link Manifest.permission#BLUETOOTH_ADMIN}
+ *            permission which can be gained with a simple
+ *            {@code <uses-permission>} manifest tag.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, FIELD})
+public @interface RequiresLegacyBluetoothAdminPermission {
+}
diff --git a/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothPermission.java b/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothPermission.java
new file mode 100644
index 0000000..79621c3
--- /dev/null
+++ b/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothPermission.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.Manifest;
+import android.os.Build;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc For apps targeting {@link Build.VERSION_CODES#R} or lower, this
+ *            requires the {@link Manifest.permission#BLUETOOTH} permission
+ *            which can be gained with a simple {@code <uses-permission>}
+ *            manifest tag.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, FIELD})
+public @interface RequiresLegacyBluetoothPermission {
+}
diff --git a/core/java/android/bluetooth/le/AdvertisingSet.java b/core/java/android/bluetooth/le/AdvertisingSet.java
index 1df35e1..54a18e6 100644
--- a/core/java/android/bluetooth/le/AdvertisingSet.java
+++ b/core/java/android/bluetooth/le/AdvertisingSet.java
@@ -16,9 +16,12 @@
 
 package android.bluetooth.le;
 
+import android.annotation.RequiresPermission;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.IBluetoothGatt;
 import android.bluetooth.IBluetoothManager;
+import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -27,9 +30,6 @@
  * <p>
  * To get an instance of {@link AdvertisingSet}, call the
  * {@link BluetoothLeAdvertiser#startAdvertisingSet} method.
- * <p>
- * <b>Note:</b> Most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
- * permission.
  *
  * @see AdvertiseData
  */
@@ -58,8 +58,6 @@
     /**
      * Enables Advertising. This method returns immediately, the operation status is
      * delivered through {@code callback.onAdvertisingEnabled()}.
-     * <p>
-     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
      *
      * @param enable whether the advertising should be enabled (true), or disabled (false)
      * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 65535
@@ -68,6 +66,9 @@
      * controller shall attempt to send prior to terminating the extended advertising, even if the
      * duration has not expired. Valid range is from 1 to 255.
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothAdvertisePermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
     public void enableAdvertising(boolean enable, int duration,
             int maxExtendedAdvertisingEvents) {
         try {
@@ -90,6 +91,9 @@
      * three bytes will be added for flags. If the update takes place when the advertising set is
      * enabled, the data can be maximum 251 bytes long.
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothAdvertisePermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
     public void setAdvertisingData(AdvertiseData advertiseData) {
         try {
             mGatt.setAdvertisingData(mAdvertiserId, advertiseData);
@@ -107,6 +111,9 @@
      * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the update takes place
      * when the advertising set is enabled, the data can be maximum 251 bytes long.
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothAdvertisePermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
     public void setScanResponseData(AdvertiseData scanResponse) {
         try {
             mGatt.setScanResponseData(mAdvertiserId, scanResponse);
@@ -122,6 +129,9 @@
      *
      * @param parameters advertising set parameters.
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothAdvertisePermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
     public void setAdvertisingParameters(AdvertisingSetParameters parameters) {
         try {
             mGatt.setAdvertisingParameters(mAdvertiserId, parameters);
@@ -135,6 +145,9 @@
      * periodic advertising is not enabled. This method returns immediately, the operation
      * status is delivered through {@code callback.onPeriodicAdvertisingParametersUpdated()}.
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothAdvertisePermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
     public void setPeriodicAdvertisingParameters(PeriodicAdvertisingParameters parameters) {
         try {
             mGatt.setPeriodicAdvertisingParameters(mAdvertiserId, parameters);
@@ -153,6 +166,9 @@
      * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the update takes place when the
      * periodic advertising is enabled for this set, the data can be maximum 251 bytes long.
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothAdvertisePermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
     public void setPeriodicAdvertisingData(AdvertiseData periodicData) {
         try {
             mGatt.setPeriodicAdvertisingData(mAdvertiserId, periodicData);
@@ -168,6 +184,9 @@
      * @param enable whether the periodic advertising should be enabled (true), or disabled
      * (false).
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothAdvertisePermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
     public void setPeriodicAdvertisingEnabled(boolean enable) {
         try {
             mGatt.setPeriodicAdvertisingEnable(mAdvertiserId, enable);
@@ -181,10 +200,9 @@
      * This method is exposed only for Bluetooth PTS tests, no app or system service
      * should ever use it.
      *
-     * This method requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission.
-     *
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public void getOwnAddress() {
         try {
             mGatt.getOwnAddress(mAdvertiserId);
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index 5f166f4..de11869 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -16,11 +16,15 @@
 
 package android.bluetooth.le;
 
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetoothGatt;
 import android.bluetooth.IBluetoothManager;
+import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.ParcelUuid;
@@ -38,9 +42,6 @@
  * <p>
  * To get an instance of {@link BluetoothLeAdvertiser}, call the
  * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method.
- * <p>
- * <b>Note:</b> Most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
- * permission.
  *
  * @see AdvertiseData
  */
@@ -81,13 +82,17 @@
     /**
      * Start Bluetooth LE Advertising. On success, the {@code advertiseData} will be broadcasted.
      * Returns immediately, the operation status is delivered through {@code callback}.
-     * <p>
-     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
      *
      * @param settings Settings for Bluetooth LE advertising.
      * @param advertiseData Advertisement data to be broadcasted.
      * @param callback Callback for advertising status.
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothAdvertisePermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_ADVERTISE,
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+    })
     public void startAdvertising(AdvertiseSettings settings,
             AdvertiseData advertiseData, final AdvertiseCallback callback) {
         startAdvertising(settings, advertiseData, null, callback);
@@ -98,14 +103,18 @@
      * operation succeeds. The {@code scanResponse} is returned when a scanning device sends an
      * active scan request. This method returns immediately, the operation status is delivered
      * through {@code callback}.
-     * <p>
-     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
      *
      * @param settings Settings for Bluetooth LE advertising.
      * @param advertiseData Advertisement data to be advertised in advertisement packet.
      * @param scanResponse Scan response associated with the advertisement data.
      * @param callback Callback for advertising status.
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothAdvertisePermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_ADVERTISE,
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+    })
     public void startAdvertising(AdvertiseSettings settings,
             AdvertiseData advertiseData, AdvertiseData scanResponse,
             final AdvertiseCallback callback) {
@@ -160,9 +169,11 @@
         }
     }
 
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     AdvertisingSetCallback wrapOldCallback(AdvertiseCallback callback, AdvertiseSettings settings) {
         return new AdvertisingSetCallback() {
             @Override
+            @SuppressLint("AndroidFrameworkRequiresPermission")
             public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower,
                     int status) {
                 if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
@@ -175,6 +186,7 @@
 
             /* Legacy advertiser is disabled on timeout */
             @Override
+            @SuppressLint("AndroidFrameworkRequiresPermission")
             public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enabled,
                     int status) {
                 if (enabled) {
@@ -192,11 +204,12 @@
     /**
      * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in
      * {@link BluetoothLeAdvertiser#startAdvertising}.
-     * <p>
-     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
      *
      * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop.
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothAdvertisePermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
     public void stopAdvertising(final AdvertiseCallback callback) {
         synchronized (mLegacyAdvertisers) {
             if (callback == null) {
@@ -232,6 +245,12 @@
      * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising
      * feature is made when it's not supported by the controller.
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothAdvertisePermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_ADVERTISE,
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+    })
     public void startAdvertisingSet(AdvertisingSetParameters parameters,
             AdvertiseData advertiseData, AdvertiseData scanResponse,
             PeriodicAdvertisingParameters periodicParameters,
@@ -262,6 +281,12 @@
      * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising
      * feature is made when it's not supported by the controller.
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothAdvertisePermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_ADVERTISE,
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+    })
     public void startAdvertisingSet(AdvertisingSetParameters parameters,
             AdvertiseData advertiseData, AdvertiseData scanResponse,
             PeriodicAdvertisingParameters periodicParameters,
@@ -297,6 +322,12 @@
      * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising
      * feature is made when it's not supported by the controller.
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothAdvertisePermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_ADVERTISE,
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+    })
     public void startAdvertisingSet(AdvertisingSetParameters parameters,
             AdvertiseData advertiseData, AdvertiseData scanResponse,
             PeriodicAdvertisingParameters periodicParameters,
@@ -337,6 +368,12 @@
      * maxExtendedAdvertisingEvents is used on a controller that doesn't support the LE Extended
      * Advertising
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothAdvertisePermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_ADVERTISE,
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+    })
     public void startAdvertisingSet(AdvertisingSetParameters parameters,
             AdvertiseData advertiseData, AdvertiseData scanResponse,
             PeriodicAdvertisingParameters periodicParameters,
@@ -445,6 +482,9 @@
      * Used to dispose of a {@link AdvertisingSet} object, obtained with {@link
      * BluetoothLeAdvertiser#startAdvertisingSet}.
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothAdvertisePermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
     public void stopAdvertisingSet(AdvertisingSetCallback callback) {
         if (callback == null) {
             throw new IllegalArgumentException("callback cannot be null");
@@ -476,6 +516,7 @@
     }
 
     // Compute the size of advertisement data or scan resp
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     private int totalBytes(AdvertiseData data, boolean isFlagsIncluded) {
         if (data == null) return 0;
         // Flags field is omitted if the advertising is not connectable.
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index 2601cd4..4271a90 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -20,12 +20,16 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothGatt;
 import android.bluetooth.IBluetoothGatt;
 import android.bluetooth.IBluetoothManager;
+import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
+import android.bluetooth.annotations.RequiresBluetoothScanPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
 import android.content.AttributionSource;
 import android.os.Handler;
 import android.os.Looper;
@@ -45,9 +49,6 @@
  * <p>
  * Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of
  * {@link BluetoothLeScanner}.
- * <p>
- * <b>Note:</b> Most of the scan methods here require
- * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
  *
  * @see ScanFilter
  */
@@ -117,7 +118,10 @@
      * @param callback Callback used to deliver scan results.
      * @throws IllegalArgumentException If {@code callback} is null.
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothScanPermission
+    @RequiresBluetoothLocationPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     public void startScan(final ScanCallback callback) {
         startScan(null, new ScanSettings.Builder().build(), callback);
     }
@@ -139,7 +143,10 @@
      * @param callback Callback used to deliver scan results.
      * @throws IllegalArgumentException If {@code settings} or {@code callback} is null.
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothScanPermission
+    @RequiresBluetoothLocationPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     public void startScan(List<ScanFilter> filters, ScanSettings settings,
             final ScanCallback callback) {
         startScan(filters, settings, null, callback, /*callbackIntent=*/ null, null);
@@ -168,7 +175,10 @@
      * could not be sent.
      * @see #stopScan(PendingIntent)
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothScanPermission
+    @RequiresBluetoothLocationPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     public int startScan(@Nullable List<ScanFilter> filters, @Nullable ScanSettings settings,
             @NonNull PendingIntent callbackIntent) {
         return startScan(filters,
@@ -186,8 +196,13 @@
      * @hide
      */
     @SystemApi
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothScanPermission
+    @RequiresBluetoothLocationPermission
     @RequiresPermission(allOf = {
-            Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.UPDATE_DEVICE_STATS})
+            android.Manifest.permission.BLUETOOTH_SCAN,
+            android.Manifest.permission.UPDATE_DEVICE_STATS
+    })
     public void startScanFromSource(final WorkSource workSource, final ScanCallback callback) {
         startScanFromSource(null, new ScanSettings.Builder().build(), workSource, callback);
     }
@@ -204,13 +219,20 @@
      * @hide
      */
     @SystemApi
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothScanPermission
+    @RequiresBluetoothLocationPermission
     @RequiresPermission(allOf = {
-            Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.UPDATE_DEVICE_STATS})
+            android.Manifest.permission.BLUETOOTH_SCAN,
+            android.Manifest.permission.UPDATE_DEVICE_STATS
+    })
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public void startScanFromSource(List<ScanFilter> filters, ScanSettings settings,
             final WorkSource workSource, final ScanCallback callback) {
         startScan(filters, settings, workSource, callback, null, null);
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     private int startScan(List<ScanFilter> filters, ScanSettings settings,
             final WorkSource workSource, final ScanCallback callback,
             final PendingIntent callbackIntent,
@@ -268,7 +290,9 @@
      *
      * @param callback
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothScanPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     public void stopScan(ScanCallback callback) {
         BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
         synchronized (mLeScanClients) {
@@ -289,7 +313,9 @@
      * @param callbackIntent The PendingIntent that was used to start the scan.
      * @see #startScan(List, ScanSettings, PendingIntent)
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothScanPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     public void stopScan(PendingIntent callbackIntent) {
         BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
         IBluetoothGatt gatt;
@@ -308,6 +334,9 @@
      * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one
      * used to start scan.
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothScanPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     public void flushPendingScanResults(ScanCallback callback) {
         BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
         if (callback == null) {
@@ -328,6 +357,7 @@
      * @hide
      */
     @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     public void startTruncatedScan(List<TruncatedFilter> truncatedFilters, ScanSettings settings,
             final ScanCallback callback) {
         int filterSize = truncatedFilters.size();
@@ -382,6 +412,8 @@
             mResultStorages = resultStorages;
         }
 
+        @SuppressLint("AndroidFrameworkRequiresPermission")
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
         public void startRegistration() {
             synchronized (this) {
                 // Scan stopped.
@@ -409,6 +441,8 @@
             }
         }
 
+        @SuppressLint("AndroidFrameworkRequiresPermission")
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
         public void stopLeScan() {
             synchronized (this) {
                 if (mScannerId <= 0) {
@@ -425,6 +459,8 @@
             }
         }
 
+        @SuppressLint("AndroidFrameworkRequiresPermission")
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
         void flushPendingBatchResults() {
             synchronized (this) {
                 if (mScannerId <= 0) {
@@ -443,6 +479,7 @@
          * Application interface registered - app is ready to go
          */
         @Override
+        @SuppressLint("AndroidFrameworkRequiresPermission")
         public void onScannerRegistered(int status, int scannerId) {
             Log.d(TAG, "onScannerRegistered() - status=" + status
                     + " scannerId=" + scannerId + " mScannerId=" + mScannerId);
@@ -595,6 +632,7 @@
         return true;
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     private boolean isHardwareResourcesAvailableForScan(ScanSettings settings) {
         final int callbackType = settings.getCallbackType();
         if ((callbackType & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0
diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java b/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java
index 0f1a8e9..9ea6c48 100644
--- a/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java
+++ b/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java
@@ -16,10 +16,14 @@
 
 package android.bluetooth.le;
 
+import android.annotation.RequiresPermission;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.IBluetoothGatt;
 import android.bluetooth.IBluetoothManager;
+import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
+import android.bluetooth.annotations.RequiresBluetoothScanPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
@@ -35,9 +39,6 @@
  * <p>
  * Use {@link BluetoothAdapter#getPeriodicAdvertisingManager()} to get an
  * instance of {@link PeriodicAdvertisingManager}.
- * <p>
- * <b>Note:</b> Most of the methods here require
- * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
  *
  * @hide
  */
@@ -89,6 +90,10 @@
      * @throws IllegalArgumentException if {@code scanResult} is null or {@code skip} is invalid or
      * {@code timeout} is invalid or {@code callback} is null.
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothScanPermission
+    @RequiresBluetoothLocationPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     public void registerSync(ScanResult scanResult, int skip, int timeout,
             PeriodicAdvertisingCallback callback) {
         registerSync(scanResult, skip, timeout, callback, null);
@@ -113,6 +118,10 @@
      * @throws IllegalArgumentException if {@code scanResult} is null or {@code skip} is invalid or
      * {@code timeout} is invalid or {@code callback} is null.
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothScanPermission
+    @RequiresBluetoothLocationPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     public void registerSync(ScanResult scanResult, int skip, int timeout,
             PeriodicAdvertisingCallback callback, Handler handler) {
         if (callback == null) {
@@ -170,6 +179,9 @@
      * @throws IllegalArgumentException if {@code callback} is null, or not a properly registered
      * callback.
      */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothScanPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     public void unregisterSync(PeriodicAdvertisingCallback callback) {
         if (callback == null) {
             throw new IllegalArgumentException("callback can't be null");
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 5ff1124..86a8a9d 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -8923,6 +8923,8 @@
         private final @ParseFlags int mFlags;
         private AssetManager mCachedAssetManager;
 
+        private ApkAssets mBaseApkAssets;
+
         DefaultSplitAssetLoader(PackageLite pkg, @ParseFlags int flags) {
             mBaseCodePath = pkg.baseCodePath;
             mSplitCodePaths = pkg.splitCodePaths;
@@ -8953,9 +8955,11 @@
             ApkAssets[] apkAssets = new ApkAssets[(mSplitCodePaths != null
                     ? mSplitCodePaths.length : 0) + 1];
 
+            mBaseApkAssets = loadApkAssets(mBaseCodePath, mFlags);
+
             // Load the base.
             int splitIdx = 0;
-            apkAssets[splitIdx++] = loadApkAssets(mBaseCodePath, mFlags);
+            apkAssets[splitIdx++] = mBaseApkAssets;
 
             // Load any splits.
             if (!ArrayUtils.isEmpty(mSplitCodePaths)) {
@@ -8982,6 +8986,11 @@
         public void close() throws Exception {
             IoUtils.closeQuietly(mCachedAssetManager);
         }
+
+        @Override
+        public ApkAssets getBaseApkAssets() {
+            return mBaseApkAssets;
+        }
     }
 
     /**
@@ -9085,5 +9094,10 @@
                 IoUtils.closeQuietly(assets);
             }
         }
+
+        @Override
+        public ApkAssets getBaseApkAssets() {
+            return mCachedSplitApks[0][0];
+        }
     }
 }
diff --git a/core/java/android/content/pm/PackagePartitions.java b/core/java/android/content/pm/PackagePartitions.java
index 98a20f7..52ee4de 100644
--- a/core/java/android/content/pm/PackagePartitions.java
+++ b/core/java/android/content/pm/PackagePartitions.java
@@ -47,7 +47,7 @@
     public static final int PARTITION_PRODUCT = 4;
     public static final int PARTITION_SYSTEM_EXT = 5;
 
-    @IntDef(flag = true, prefix = { "PARTITION_" }, value = {
+    @IntDef(prefix = { "PARTITION_" }, value = {
         PARTITION_SYSTEM,
         PARTITION_VENDOR,
         PARTITION_ODM,
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index a1ffc0c..0fc6b2b 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -383,10 +383,9 @@
         }
 
         try {
-            final AssetManager assets = assetLoader.getBaseAssetManager();
             final File baseApk = new File(lite.getBaseApkPath());
             final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
-                    lite.getPath(), assets, flags);
+                    lite.getPath(), assetLoader, flags);
             if (result.isError()) {
                 return input.error(result);
             }
@@ -442,7 +441,7 @@
             final ParseResult<ParsingPackage> result = parseBaseApk(input,
                     apkFile,
                     apkFile.getCanonicalPath(),
-                    assetLoader.getBaseAssetManager(), flags);
+                    assetLoader, flags);
             if (result.isError()) {
                 return input.error(result);
             }
@@ -458,7 +457,8 @@
     }
 
     private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile,
-            String codePath, AssetManager assets, int flags) {
+            String codePath, SplitAssetLoader assetLoader, int flags)
+            throws PackageParserException {
         final String apkPath = apkFile.getAbsolutePath();
 
         String volumeUuid = null;
@@ -469,6 +469,7 @@
 
         if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
 
+        final AssetManager assets = assetLoader.getBaseAssetManager();
         final int cookie = assets.findCookieForPath(apkPath);
         if (cookie == 0) {
             return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST,
@@ -500,12 +501,19 @@
                 }
             }
 
-            ApkAssets apkAssets = assets.getApkAssets()[0];
-            if (apkAssets.definesOverlayable()) {
+            ApkAssets apkAssets = assetLoader.getBaseApkAssets();
+            boolean definesOverlayable = false;
+            try {
+                definesOverlayable = apkAssets.definesOverlayable();
+            } catch (IOException ignored) {
+                // Will fail if there's no packages in the ApkAssets, which can be treated as false
+            }
+
+            if (definesOverlayable) {
                 SparseArray<String> packageNames = assets.getAssignedPackageIdentifiers();
                 int size = packageNames.size();
                 for (int index = 0; index < size; index++) {
-                    String packageName = packageNames.get(index);
+                    String packageName = packageNames.valueAt(index);
                     Map<String, String> overlayableToActor = assets.getOverlayableMap(packageName);
                     if (overlayableToActor != null && !overlayableToActor.isEmpty()) {
                         for (String overlayable : overlayableToActor.keySet()) {
@@ -2799,7 +2807,15 @@
         }
     }
 
+    @SuppressWarnings("AndroidFrameworkCompatChange")
     private void convertSplitPermissions(ParsingPackage pkg) {
+        // STOPSHIP(b/183905675): REMOVE THIS TERRIBLE, HORRIBLE, NO GOOD, VERY BAD HACK
+        if ("com.android.chrome".equals(pkg.getPackageName())
+                && pkg.getVersionCode() <= 445500399
+                && pkg.getTargetSdkVersion() > Build.VERSION_CODES.R) {
+            pkg.setTargetSdkVersion(Build.VERSION_CODES.R);
+        }
+
         final int listSize = mSplitPermissionInfos.size();
         for (int is = 0; is < listSize; is++) {
             final PermissionManager.SplitPermissionInfo spi = mSplitPermissionInfos.get(is);
diff --git a/core/java/android/content/pm/parsing/component/ParsedAttribution.java b/core/java/android/content/pm/parsing/component/ParsedAttribution.java
index 4ec2e73..3a4aae1 100644
--- a/core/java/android/content/pm/parsing/component/ParsedAttribution.java
+++ b/core/java/android/content/pm/parsing/component/ParsedAttribution.java
@@ -40,7 +40,7 @@
     public static final int MAX_ATTRIBUTION_TAG_LEN = 50;
 
     /** Maximum amount of attributions per package */
-    private static final int MAX_NUM_ATTRIBUTIONS = 10000;
+    private static final int MAX_NUM_ATTRIBUTIONS = 1000;
 
     /** Tag of the attribution */
     public final @NonNull String tag;
@@ -100,7 +100,7 @@
 
 
 
-    // Code below generated by codegen v1.0.23.
+    // Code below generated by codegen v1.0.22.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
@@ -215,8 +215,8 @@
     };
 
     @DataClass.Generated(
-            time = 1618351459610L,
-            codegenVersion = "1.0.23",
+            time = 1607463855175L,
+            codegenVersion = "1.0.22",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedAttribution.java",
             inputSignatures = "public static final  int MAX_ATTRIBUTION_TAG_LEN\nprivate static final  int MAX_NUM_ATTRIBUTIONS\npublic final @android.annotation.NonNull java.lang.String tag\npublic final @android.annotation.StringRes int label\npublic final @android.annotation.NonNull java.util.List<java.lang.String> inheritFrom\npublic static  boolean isCombinationValid(java.util.List<android.content.pm.parsing.component.ParsedAttribution>)\nclass ParsedAttribution extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=false)")
     @Deprecated
diff --git a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
index f3caf60..c1a8396 100644
--- a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
+++ b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
@@ -43,6 +43,8 @@
     private final @ParseFlags int mFlags;
     private AssetManager mCachedAssetManager;
 
+    private ApkAssets mBaseApkAssets;
+
     public DefaultSplitAssetLoader(PackageLite pkg, @ParseFlags int flags) {
         mBaseApkPath = pkg.getBaseApkPath();
         mSplitApkPaths = pkg.getSplitApkPaths();
@@ -76,7 +78,7 @@
 
         // Load the base.
         int splitIdx = 0;
-        apkAssets[splitIdx++] = loadApkAssets(mBaseApkPath, mFlags);
+        apkAssets[splitIdx++] = mBaseApkAssets = loadApkAssets(mBaseApkPath, mFlags);
 
         // Load any splits.
         if (!ArrayUtils.isEmpty(mSplitApkPaths)) {
@@ -100,6 +102,11 @@
     }
 
     @Override
+    public ApkAssets getBaseApkAssets() {
+        return mBaseApkAssets;
+    }
+
+    @Override
     public void close() throws Exception {
         IoUtils.closeQuietly(mCachedAssetManager);
     }
diff --git a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
index 523ca40..e5c2158 100644
--- a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
+++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
@@ -128,6 +128,11 @@
     }
 
     @Override
+    public ApkAssets getBaseApkAssets() {
+        return mCachedSplitApks[0][0];
+    }
+
+    @Override
     public void close() throws Exception {
         for (AssetManager assets : mCachedAssetManagers) {
             IoUtils.closeQuietly(assets);
diff --git a/core/java/android/content/pm/split/SplitAssetLoader.java b/core/java/android/content/pm/split/SplitAssetLoader.java
index 108fb95..7584e15f 100644
--- a/core/java/android/content/pm/split/SplitAssetLoader.java
+++ b/core/java/android/content/pm/split/SplitAssetLoader.java
@@ -16,6 +16,7 @@
 package android.content.pm.split;
 
 import android.content.pm.PackageParser;
+import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
 
 /**
@@ -27,4 +28,6 @@
 public interface SplitAssetLoader extends AutoCloseable {
     AssetManager getBaseAssetManager() throws PackageParser.PackageParserException;
     AssetManager getSplitAssetManager(int splitIdx) throws PackageParser.PackageParserException;
+
+    ApkAssets getBaseApkAssets();
 }
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 3c11d8e..bc2dcb3 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -571,10 +571,10 @@
         }
 
         int sensorHandle = (sensor == null) ? -1 : sensor.getHandle();
-        if (Compatibility.isChangeEnabled(CHANGE_ID_SAMPLING_RATE_SENSORS_PERMISSION)
-                && rate > CAPPED_SAMPLING_RATE_LEVEL
+        if (rate > CAPPED_SAMPLING_RATE_LEVEL
                 && mIsPackageDebuggable
-                && !mHasHighSamplingRateSensorsPermission) {
+                && !mHasHighSamplingRateSensorsPermission
+                && Compatibility.isChangeEnabled(CHANGE_ID_SAMPLING_RATE_SENSORS_PERMISSION)) {
             throw new SecurityException("To use the sampling rate level " + rate
                     + ", app needs to declare the normal permission"
                     + " HIGH_SAMPLING_RATE_SENSORS.");
@@ -782,10 +782,10 @@
                 Sensor sensor, int rateUs, int maxBatchReportLatencyUs) {
             if (mNativeSensorEventQueue == 0) throw new NullPointerException();
             if (sensor == null) throw new NullPointerException();
-            if (Compatibility.isChangeEnabled(CHANGE_ID_SAMPLING_RATE_SENSORS_PERMISSION)
-                    && rateUs < CAPPED_SAMPLING_PERIOD_US
+            if (rateUs < CAPPED_SAMPLING_PERIOD_US
                     && mManager.mIsPackageDebuggable
-                    && !mManager.mHasHighSamplingRateSensorsPermission) {
+                    && !mManager.mHasHighSamplingRateSensorsPermission
+                    && Compatibility.isChangeEnabled(CHANGE_ID_SAMPLING_RATE_SENSORS_PERMISSION)) {
                 throw new SecurityException("To use the sampling rate of " + rateUs
                         + " microseconds, app needs to declare the normal permission"
                         + " HIGH_SAMPLING_RATE_SENSORS.");
diff --git a/core/java/android/hardware/display/DeviceProductInfo.java b/core/java/android/hardware/display/DeviceProductInfo.java
index 11c426a..835b3fd 100644
--- a/core/java/android/hardware/display/DeviceProductInfo.java
+++ b/core/java/android/hardware/display/DeviceProductInfo.java
@@ -74,12 +74,26 @@
             Integer modelYear,
             ManufactureDate manufactureDate,
             int connectionToSinkType) {
-        this.mName = name;
-        this.mManufacturerPnpId = manufacturerPnpId;
-        this.mProductId = productId;
-        this.mModelYear = modelYear;
-        this.mManufactureDate = manufactureDate;
-        this.mConnectionToSinkType = connectionToSinkType;
+        mName = name;
+        mManufacturerPnpId = manufacturerPnpId;
+        mProductId = productId;
+        mModelYear = modelYear;
+        mManufactureDate = manufactureDate;
+        mConnectionToSinkType = connectionToSinkType;
+    }
+
+    public DeviceProductInfo(
+            @Nullable String name,
+            @NonNull String manufacturerPnpId,
+            @NonNull String productId,
+            @IntRange(from = 1990) int modelYear,
+            @ConnectionToSinkType int connectionToSinkType) {
+        mName = name;
+        mManufacturerPnpId = Objects.requireNonNull(manufacturerPnpId);
+        mProductId = Objects.requireNonNull(productId);
+        mModelYear = modelYear;
+        mManufactureDate = null;
+        mConnectionToSinkType = connectionToSinkType;
     }
 
     private DeviceProductInfo(Parcel in) {
@@ -100,6 +114,9 @@
     }
 
     /**
+     * Returns the Manufacturer Plug and Play ID. This ID identifies the manufacture according to
+     * the list: https://uefi.org/PNP_ID_List. It consist of 3 characters, each character
+     * is an uppercase letter (A-Z).
      * @return Manufacturer Plug and Play ID.
      */
     @NonNull
diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
index 5f65d46..662ebb3 100644
--- a/core/java/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -78,10 +78,8 @@
 
     /**
      * An IPsec VPN created by the built-in LegacyVpnRunner.
-     * @deprecated new Android devices should use VPN_TYPE_PLATFORM instead.
      * @hide
      */
-    @Deprecated
     @SystemApi(client = MODULE_LIBRARIES)
     public static final int TYPE_VPN_LEGACY = 3;
 
@@ -418,4 +416,4 @@
             throw e.rethrowFromSystemServer();
         }
     }
-}
\ No newline at end of file
+}
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index d5cc01a..cb9a3e4 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -24,7 +24,7 @@
 import android.nfc.TechListParcel;
 import android.nfc.IAppCallback;
 import android.nfc.INfcAdapterExtras;
-import android.nfc.INfcControllerAlwaysOnStateCallback;
+import android.nfc.INfcControllerAlwaysOnListener;
 import android.nfc.INfcTag;
 import android.nfc.INfcCardEmulation;
 import android.nfc.INfcFCardEmulation;
@@ -76,6 +76,6 @@
     boolean setControllerAlwaysOn(boolean value);
     boolean isControllerAlwaysOn();
     boolean isControllerAlwaysOnSupported();
-    void registerControllerAlwaysOnStateCallback(in INfcControllerAlwaysOnStateCallback callback);
-    void unregisterControllerAlwaysOnStateCallback(in INfcControllerAlwaysOnStateCallback callback);
+    void registerControllerAlwaysOnListener(in INfcControllerAlwaysOnListener listener);
+    void unregisterControllerAlwaysOnListener(in INfcControllerAlwaysOnListener listener);
 }
diff --git a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl b/core/java/android/nfc/INfcControllerAlwaysOnListener.aidl
similarity index 87%
rename from core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
rename to core/java/android/nfc/INfcControllerAlwaysOnListener.aidl
index 1e4fdd7..1bb7680 100644
--- a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
+++ b/core/java/android/nfc/INfcControllerAlwaysOnListener.aidl
@@ -19,11 +19,11 @@
 /**
  * @hide
  */
-oneway interface INfcControllerAlwaysOnStateCallback {
+oneway interface INfcControllerAlwaysOnListener {
   /**
    * Called whenever the controller always on state changes
    *
    * @param isEnabled true if the state is enabled, false otherwise
    */
-  void onControllerAlwaysOnStateChanged(boolean isEnabled);
+  void onControllerAlwaysOnChanged(boolean isEnabled);
 }
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index bbf802c..64c1211 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -67,7 +67,7 @@
 public final class NfcAdapter {
     static final String TAG = "NFC";
 
-    private final NfcControllerAlwaysOnStateListener mControllerAlwaysOnStateListener;
+    private final NfcControllerAlwaysOnListener mControllerAlwaysOnListener;
 
     /**
      * Intent to start an activity when a tag with NDEF payload is discovered.
@@ -418,19 +418,19 @@
     }
 
     /**
-     * A callback to be invoked when NFC controller always on state changes.
-     * <p>Register your {@code ControllerAlwaysOnStateCallback} implementation with {@link
-     * NfcAdapter#registerControllerAlwaysOnStateCallback} and disable it with {@link
-     * NfcAdapter#unregisterControllerAlwaysOnStateCallback}.
-     * @see #registerControllerAlwaysOnStateCallback
+     * A listener to be invoked when NFC controller always on state changes.
+     * <p>Register your {@code ControllerAlwaysOnListener} implementation with {@link
+     * NfcAdapter#registerControllerAlwaysOnListener} and disable it with {@link
+     * NfcAdapter#unregisterControllerAlwaysOnListener}.
+     * @see #registerControllerAlwaysOnListener
      * @hide
      */
     @SystemApi
-    public interface ControllerAlwaysOnStateCallback {
+    public interface ControllerAlwaysOnListener {
         /**
          * Called on NFC controller always on state changes
          */
-        void onStateChanged(boolean isEnabled);
+        void onControllerAlwaysOnChanged(boolean isEnabled);
     }
 
     /**
@@ -748,7 +748,7 @@
         mNfcUnlockHandlers = new HashMap<NfcUnlockHandler, INfcUnlockHandler>();
         mTagRemovedListener = null;
         mLock = new Object();
-        mControllerAlwaysOnStateListener = new NfcControllerAlwaysOnStateListener(getService());
+        mControllerAlwaysOnListener = new NfcControllerAlwaysOnListener(getService());
     }
 
     /**
@@ -2246,12 +2246,12 @@
      * <p>This API is for the NFCC internal state management. It allows to discriminate
      * the controller function from the NFC function by keeping the NFC controller on without
      * any NFC RF enabled if necessary.
-     * <p>This call is asynchronous. Register a callback {@link #ControllerAlwaysOnStateCallback}
-     * by {@link #registerControllerAlwaysOnStateCallback} to find out when the operation is
+     * <p>This call is asynchronous. Register a listener {@link #ControllerAlwaysOnListener}
+     * by {@link #registerControllerAlwaysOnListener} to find out when the operation is
      * complete.
      * <p>If this returns true, then either NFCC always on state has been set based on the value,
-     * or a {@link ControllerAlwaysOnStateCallback#onStateChanged(boolean)} will be invoked to
-     * indicate the state change.
+     * or a {@link ControllerAlwaysOnListener#onControllerAlwaysOnChanged(boolean)} will be invoked
+     * to indicate the state change.
      * If this returns false, then there is some problem that prevents an attempt to turn NFCC
      * always on.
      * @param value if true the NFCC will be kept on (with no RF enabled if NFC adapter is
@@ -2344,37 +2344,37 @@
     }
 
     /**
-     * Register a {@link ControllerAlwaysOnStateCallback} to listen for NFC controller always on
+     * Register a {@link ControllerAlwaysOnListener} to listen for NFC controller always on
      * state changes
-     * <p>The provided callback will be invoked by the given {@link Executor}.
+     * <p>The provided listener will be invoked by the given {@link Executor}.
      *
-     * @param executor an {@link Executor} to execute given callback
-     * @param callback user implementation of the {@link ControllerAlwaysOnStateCallback}
+     * @param executor an {@link Executor} to execute given listener
+     * @param listener user implementation of the {@link ControllerAlwaysOnListener}
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
-    public void registerControllerAlwaysOnStateCallback(
+    public void registerControllerAlwaysOnListener(
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull ControllerAlwaysOnStateCallback callback) {
-        mControllerAlwaysOnStateListener.register(executor, callback);
+            @NonNull ControllerAlwaysOnListener listener) {
+        mControllerAlwaysOnListener.register(executor, listener);
     }
 
     /**
-     * Unregister the specified {@link ControllerAlwaysOnStateCallback}
-     * <p>The same {@link ControllerAlwaysOnStateCallback} object used when calling
-     * {@link #registerControllerAlwaysOnStateCallback(Executor, ControllerAlwaysOnStateCallback)}
+     * Unregister the specified {@link ControllerAlwaysOnListener}
+     * <p>The same {@link ControllerAlwaysOnListener} object used when calling
+     * {@link #registerControllerAlwaysOnListener(Executor, ControllerAlwaysOnListener)}
      * must be used.
      *
-     * <p>Callbacks are automatically unregistered when application process goes away
+     * <p>Listeners are automatically unregistered when application process goes away
      *
-     * @param callback user implementation of the {@link ControllerAlwaysOnStateCallback}
+     * @param listener user implementation of the {@link ControllerAlwaysOnListener}
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
-    public void unregisterControllerAlwaysOnStateCallback(
-            @NonNull ControllerAlwaysOnStateCallback callback) {
-        mControllerAlwaysOnStateListener.unregister(callback);
+    public void unregisterControllerAlwaysOnListener(
+            @NonNull ControllerAlwaysOnListener listener) {
+        mControllerAlwaysOnListener.unregister(listener);
     }
 }
diff --git a/core/java/android/nfc/NfcControllerAlwaysOnListener.java b/core/java/android/nfc/NfcControllerAlwaysOnListener.java
new file mode 100644
index 0000000..96707bb
--- /dev/null
+++ b/core/java/android/nfc/NfcControllerAlwaysOnListener.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+import android.annotation.NonNull;
+import android.nfc.NfcAdapter.ControllerAlwaysOnListener;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+public class NfcControllerAlwaysOnListener extends INfcControllerAlwaysOnListener.Stub {
+    private static final String TAG = NfcControllerAlwaysOnListener.class.getSimpleName();
+
+    private final INfcAdapter mAdapter;
+
+    private final Map<ControllerAlwaysOnListener, Executor> mListenerMap = new HashMap<>();
+
+    private boolean mCurrentState = false;
+    private boolean mIsRegistered = false;
+
+    public NfcControllerAlwaysOnListener(@NonNull INfcAdapter adapter) {
+        mAdapter = adapter;
+    }
+
+    /**
+     * Register a {@link ControllerAlwaysOnListener} with this
+     * {@link NfcControllerAlwaysOnListener}
+     *
+     * @param executor an {@link Executor} to execute given listener
+     * @param listener user implementation of the {@link ControllerAlwaysOnListener}
+     */
+    public void register(@NonNull Executor executor,
+            @NonNull ControllerAlwaysOnListener listener) {
+        synchronized (this) {
+            if (mListenerMap.containsKey(listener)) {
+                return;
+            }
+
+            mListenerMap.put(listener, executor);
+            if (!mIsRegistered) {
+                try {
+                    mAdapter.registerControllerAlwaysOnListener(this);
+                    mIsRegistered = true;
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to register");
+                }
+            }
+        }
+    }
+
+    /**
+     * Unregister the specified {@link ControllerAlwaysOnListener}
+     *
+     * @param listener user implementation of the {@link ControllerAlwaysOnListener}
+     */
+    public void unregister(@NonNull ControllerAlwaysOnListener listener) {
+        synchronized (this) {
+            if (!mListenerMap.containsKey(listener)) {
+                return;
+            }
+
+            mListenerMap.remove(listener);
+
+            if (mListenerMap.isEmpty() && mIsRegistered) {
+                try {
+                    mAdapter.unregisterControllerAlwaysOnListener(this);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to unregister");
+                }
+                mIsRegistered = false;
+            }
+        }
+    }
+
+    private void sendCurrentState(@NonNull ControllerAlwaysOnListener listener) {
+        synchronized (this) {
+            Executor executor = mListenerMap.get(listener);
+
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                executor.execute(() -> listener.onControllerAlwaysOnChanged(
+                        mCurrentState));
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    @Override
+    public void onControllerAlwaysOnChanged(boolean isEnabled) {
+        synchronized (this) {
+            mCurrentState = isEnabled;
+            for (ControllerAlwaysOnListener cb : mListenerMap.keySet()) {
+                sendCurrentState(cb);
+            }
+        }
+    }
+}
+
diff --git a/core/java/android/nfc/NfcControllerAlwaysOnStateListener.java b/core/java/android/nfc/NfcControllerAlwaysOnStateListener.java
deleted file mode 100644
index 69a9ec7..0000000
--- a/core/java/android/nfc/NfcControllerAlwaysOnStateListener.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nfc;
-
-import android.annotation.NonNull;
-import android.nfc.NfcAdapter.ControllerAlwaysOnStateCallback;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executor;
-
-/**
- * @hide
- */
-public class NfcControllerAlwaysOnStateListener extends INfcControllerAlwaysOnStateCallback.Stub {
-    private static final String TAG = "NfcControllerAlwaysOnStateListener";
-
-    private final INfcAdapter mAdapter;
-
-    private final Map<ControllerAlwaysOnStateCallback, Executor> mCallbackMap = new HashMap<>();
-
-    private boolean mCurrentState = false;
-    private boolean mIsRegistered = false;
-
-    public NfcControllerAlwaysOnStateListener(@NonNull INfcAdapter adapter) {
-        mAdapter = adapter;
-    }
-
-    /**
-     * Register a {@link ControllerAlwaysOnStateCallback} with this
-     * {@link NfcControllerAlwaysOnStateListener}
-     *
-     * @param executor an {@link Executor} to execute given callback
-     * @param callback user implementation of the {@link ControllerAlwaysOnStateCallback}
-     */
-    public void register(@NonNull Executor executor,
-            @NonNull ControllerAlwaysOnStateCallback callback) {
-        synchronized (this) {
-            if (mCallbackMap.containsKey(callback)) {
-                return;
-            }
-
-            mCallbackMap.put(callback, executor);
-            if (!mIsRegistered) {
-                try {
-                    mAdapter.registerControllerAlwaysOnStateCallback(this);
-                    mIsRegistered = true;
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Failed to register ControllerAlwaysOnStateListener");
-                }
-            }
-        }
-    }
-
-    /**
-     * Unregister the specified {@link ControllerAlwaysOnStateCallback}
-     *
-     * @param callback user implementation of the {@link ControllerAlwaysOnStateCallback}
-     */
-    public void unregister(@NonNull ControllerAlwaysOnStateCallback callback) {
-        synchronized (this) {
-            if (!mCallbackMap.containsKey(callback)) {
-                return;
-            }
-
-            mCallbackMap.remove(callback);
-
-            if (mCallbackMap.isEmpty() && mIsRegistered) {
-                try {
-                    mAdapter.unregisterControllerAlwaysOnStateCallback(this);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Failed to unregister ControllerAlwaysOnStateListener");
-                }
-                mIsRegistered = false;
-            }
-        }
-    }
-
-    private void sendCurrentState(@NonNull ControllerAlwaysOnStateCallback callback) {
-        synchronized (this) {
-            Executor executor = mCallbackMap.get(callback);
-
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                executor.execute(() -> callback.onStateChanged(
-                        mCurrentState));
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-    }
-
-    @Override
-    public void onControllerAlwaysOnStateChanged(boolean isEnabled) {
-        synchronized (this) {
-            mCurrentState = isEnabled;
-            for (ControllerAlwaysOnStateCallback cb : mCallbackMap.keySet()) {
-                sendCurrentState(cb);
-            }
-        }
-    }
-}
-
diff --git a/core/java/android/nfc/TEST_MAPPING b/core/java/android/nfc/TEST_MAPPING
new file mode 100644
index 0000000..71ad687
--- /dev/null
+++ b/core/java/android/nfc/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "NfcManagerTests"
+    }
+  ]
+}
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 1a40f06..17c90d6 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -886,6 +886,24 @@
     }
 
     /**
+     * @param micMuted whether to consider the microphone muted when retrieving audio ops
+     * @return A list of permission groups currently or recently used by all apps by all users in
+     * the current profile group.
+     *
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    @RequiresPermission(Manifest.permission.GET_APP_OPS_STATS)
+    public List<PermGroupUsage> getIndicatorAppOpUsageData(boolean micMuted) {
+        // Lazily initialize the usage helper
+        if (mUsageHelper == null) {
+            mUsageHelper = new PermissionUsageHelper(mContext);
+        }
+        return mUsageHelper.getOpUsageData(micMuted);
+    }
+
+    /**
      * Determine if a package should be shown in indicators. Only a select few roles, and the
      * system app itself, are hidden. These values are updated at most every 15 seconds.
      * @hide
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index d89c29a..87fb611 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -37,6 +37,7 @@
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
+import android.graphics.BLASTBufferQueue;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
@@ -208,8 +209,8 @@
         int mCurHeight;
         float mZoom = 0f;
         int mWindowFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-        int mWindowPrivateFlags =
-                WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS;
+        int mWindowPrivateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS
+                | WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
         int mCurWindowFlags = mWindowFlags;
         int mCurWindowPrivateFlags = mWindowPrivateFlags;
         Rect mPreviewSurfacePosition;
@@ -253,6 +254,7 @@
         private int mDisplayState;
 
         SurfaceControl mSurfaceControl = new SurfaceControl();
+        BLASTBufferQueue mBlastBufferQueue;
 
         final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() {
             {
@@ -974,7 +976,14 @@
                             View.VISIBLE, 0, -1, mWinFrames, mMergedConfiguration, mSurfaceControl,
                             mInsetsState, mTempControls, mSurfaceSize);
                     if (mSurfaceControl.isValid()) {
-                        mSurfaceHolder.mSurface.copyFrom(mSurfaceControl);
+                        Surface blastSurface = getOrCreateBLASTSurface(mSurfaceSize.x,
+                                mSurfaceSize.y, mFormat);
+                        // If blastSurface == null that means it hasn't changed since the last
+                        // time we called. In this situation, avoid calling transferFrom as we
+                        // would then inc the generation ID and cause EGL resources to be recreated.
+                        if (blastSurface != null) {
+                            mSurfaceHolder.mSurface.transferFrom(blastSurface);
+                        }
                     }
                     if (!mLastSurfaceSize.equals(mSurfaceSize)) {
                         mLastSurfaceSize.set(mSurfaceSize.x, mSurfaceSize.y);
@@ -1455,13 +1464,12 @@
                 return;
             }
             Surface surface = mSurfaceHolder.getSurface();
-            boolean widthIsLarger =
-                    mSurfaceControl.getWidth() > mSurfaceControl.getHeight();
-            int smaller = widthIsLarger ? mSurfaceControl.getWidth()
-                    : mSurfaceControl.getHeight();
+            boolean widthIsLarger = mSurfaceSize.x > mSurfaceSize.y;
+            int smaller = widthIsLarger ? mSurfaceSize.x
+                    : mSurfaceSize.y;
             float ratio = (float) MIN_BITMAP_SCREENSHOT_WIDTH / (float) smaller;
-            int width = (int) (ratio * mSurfaceControl.getWidth());
-            int height = (int) (ratio * mSurfaceControl.getHeight());
+            int width = (int) (ratio * mSurfaceSize.x);
+            int height = (int) (ratio * mSurfaceSize.y);
             if (width <= 0 || height <= 0) {
                 Log.e(TAG, "wrong width and height values of bitmap " + width + " " + height);
                 return;
@@ -1842,6 +1850,21 @@
             public void onDisplayAdded(int displayId) {
             }
         };
+
+        private Surface getOrCreateBLASTSurface(int width, int height, int format) {
+            Surface ret = null;
+            if (mBlastBufferQueue == null) {
+                mBlastBufferQueue = new BLASTBufferQueue("Wallpaper", mSurfaceControl, width,
+                        height, format);
+                // We only return the Surface the first time, as otherwise
+                // it hasn't changed and there is no need to update.
+                ret = mBlastBufferQueue.createSurface();
+            } else {
+                mBlastBufferQueue.update(mSurfaceControl, width, height, format);
+            }
+
+            return ret;
+        }
     }
 
     private boolean isValid(RectF area) {
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 340fa40..4b18c5a 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -796,13 +796,14 @@
     /**
      * Notify {@link PhysicalChannelConfig} has changed for a specific subscription.
      *
+     * @param slotIndex for which physical channel configs changed.
      * @param subId the subId
      * @param configs a list of {@link PhysicalChannelConfig}, the configs of physical channel.
      */
-    public void notifyPhysicalChannelConfigForSubscriber(
-            int subId, List<PhysicalChannelConfig> configs) {
+    public void notifyPhysicalChannelConfigForSubscriber(int slotIndex, int subId,
+            List<PhysicalChannelConfig> configs) {
         try {
-            sRegistry.notifyPhysicalChannelConfigForSubscriber(subId, configs);
+            sRegistry.notifyPhysicalChannelConfigForSubscriber(slotIndex, subId, configs);
         } catch (RemoteException ex) {
             // system server crash
         }
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 0a3963d..be172f7 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -693,71 +693,79 @@
         ThreadedRenderer.setFPSDivisor(divisor);
     }
 
+    private void traceMessage(String msg) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIEW, msg);
+        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+    }
+
     void doFrame(long frameTimeNanos, int frame,
             DisplayEventReceiver.VsyncEventData vsyncEventData) {
         final long startNanos;
         final long frameIntervalNanos = vsyncEventData.frameInterval;
-        synchronized (mLock) {
-            if (!mFrameScheduled) {
-                return; // no work to do
-            }
-
-            if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
-                mDebugPrintNextFrameTimeDelta = false;
-                Log.d(TAG, "Frame time delta: "
-                        + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
-            }
-
-            long intendedFrameTimeNanos = frameTimeNanos;
-            startNanos = System.nanoTime();
-            final long jitterNanos = startNanos - frameTimeNanos;
-            if (jitterNanos >= frameIntervalNanos) {
-                final long skippedFrames = jitterNanos / frameIntervalNanos;
-                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
-                    Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
-                            + "The application may be doing too much work on its main thread.");
-                }
-                final long lastFrameOffset = jitterNanos % frameIntervalNanos;
-                if (DEBUG_JANK) {
-                    Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
-                            + "which is more than the frame interval of "
-                            + (frameIntervalNanos * 0.000001f) + " ms!  "
-                            + "Skipping " + skippedFrames + " frames and setting frame "
-                            + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
-                }
-                frameTimeNanos = startNanos - lastFrameOffset;
-            }
-
-            if (frameTimeNanos < mLastFrameTimeNanos) {
-                if (DEBUG_JANK) {
-                    Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
-                            + "previously skipped frame.  Waiting for next vsync.");
-                }
-                scheduleVsyncLocked();
-                return;
-            }
-
-            if (mFPSDivisor > 1) {
-                long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
-                if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
-                    scheduleVsyncLocked();
-                    return;
-                }
-            }
-
-            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
-                    vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
-            mFrameScheduled = false;
-            mLastFrameTimeNanos = frameTimeNanos;
-            mLastFrameIntervalNanos = frameIntervalNanos;
-            mLastVsyncEventData = vsyncEventData;
-        }
-
         try {
             if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                 Trace.traceBegin(Trace.TRACE_TAG_VIEW,
                         "Choreographer#doFrame " + vsyncEventData.id);
             }
+            synchronized (mLock) {
+                if (!mFrameScheduled) {
+                    traceMessage("Frame not scheduled");
+                    return; // no work to do
+                }
+
+                if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
+                    mDebugPrintNextFrameTimeDelta = false;
+                    Log.d(TAG, "Frame time delta: "
+                            + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
+                }
+
+                long intendedFrameTimeNanos = frameTimeNanos;
+                startNanos = System.nanoTime();
+                final long jitterNanos = startNanos - frameTimeNanos;
+                if (jitterNanos >= frameIntervalNanos) {
+                    final long skippedFrames = jitterNanos / frameIntervalNanos;
+                    if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
+                        Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
+                                + "The application may be doing too much work on its main thread.");
+                    }
+                    final long lastFrameOffset = jitterNanos % frameIntervalNanos;
+                    if (DEBUG_JANK) {
+                        Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+                                + "which is more than the frame interval of "
+                                + (frameIntervalNanos * 0.000001f) + " ms!  "
+                                + "Skipping " + skippedFrames + " frames and setting frame "
+                                + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
+                    }
+                    frameTimeNanos = startNanos - lastFrameOffset;
+                }
+
+                if (frameTimeNanos < mLastFrameTimeNanos) {
+                    if (DEBUG_JANK) {
+                        Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
+                                + "previously skipped frame.  Waiting for next vsync.");
+                    }
+                    traceMessage("Frame time goes backward");
+                    scheduleVsyncLocked();
+                    return;
+                }
+
+                if (mFPSDivisor > 1) {
+                    long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
+                    if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
+                        traceMessage("Frame skipped due to FPSDivisor");
+                        scheduleVsyncLocked();
+                        return;
+                    }
+                }
+
+                mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
+                        vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
+                mFrameScheduled = false;
+                mLastFrameTimeNanos = frameTimeNanos;
+                mLastFrameIntervalNanos = frameIntervalNanos;
+                mLastVsyncEventData = vsyncEventData;
+            }
+
             AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
 
             mFrameInfo.markInputHandlingStart();
@@ -870,7 +878,12 @@
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private void scheduleVsyncLocked() {
-        mDisplayEventReceiver.scheduleVsync();
+        try {
+            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked");
+            mDisplayEventReceiver.scheduleVsync();
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
     }
 
     private boolean isRunningOnLooperThreadLocked() {
@@ -967,32 +980,40 @@
         @Override
         public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
                 VsyncEventData vsyncEventData) {
-            // Post the vsync event to the Handler.
-            // The idea is to prevent incoming vsync events from completely starving
-            // the message queue.  If there are no messages in the queue with timestamps
-            // earlier than the frame time, then the vsync event will be processed immediately.
-            // Otherwise, messages that predate the vsync event will be handled first.
-            long now = System.nanoTime();
-            if (timestampNanos > now) {
-                Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
-                        + " ms in the future!  Check that graphics HAL is generating vsync "
-                        + "timestamps using the correct timebase.");
-                timestampNanos = now;
-            }
+            try {
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                    Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+                            "Choreographer#onVsync " + vsyncEventData.id);
+                }
+                // Post the vsync event to the Handler.
+                // The idea is to prevent incoming vsync events from completely starving
+                // the message queue.  If there are no messages in the queue with timestamps
+                // earlier than the frame time, then the vsync event will be processed immediately.
+                // Otherwise, messages that predate the vsync event will be handled first.
+                long now = System.nanoTime();
+                if (timestampNanos > now) {
+                    Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
+                            + " ms in the future!  Check that graphics HAL is generating vsync "
+                            + "timestamps using the correct timebase.");
+                    timestampNanos = now;
+                }
 
-            if (mHavePendingVsync) {
-                Log.w(TAG, "Already have a pending vsync event.  There should only be "
-                        + "one at a time.");
-            } else {
-                mHavePendingVsync = true;
-            }
+                if (mHavePendingVsync) {
+                    Log.w(TAG, "Already have a pending vsync event.  There should only be "
+                            + "one at a time.");
+                } else {
+                    mHavePendingVsync = true;
+                }
 
-            mTimestampNanos = timestampNanos;
-            mFrame = frame;
-            mLastVsyncEventData = vsyncEventData;
-            Message msg = Message.obtain(mHandler, this);
-            msg.setAsynchronous(true);
-            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
+                mTimestampNanos = timestampNanos;
+                mFrame = frame;
+                mLastVsyncEventData = vsyncEventData;
+                Message msg = Message.obtain(mHandler, this);
+                msg.setAsynchronous(true);
+                mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
         }
 
         @Override
diff --git a/core/java/android/view/ScrollCaptureConnection.java b/core/java/android/view/ScrollCaptureConnection.java
index a6d786e..5fcb011 100644
--- a/core/java/android/view/ScrollCaptureConnection.java
+++ b/core/java/android/view/ScrollCaptureConnection.java
@@ -185,7 +185,8 @@
             }
             Log.w(TAG, "close(): capture session still active! Ending now.");
             // -> UiThread
-            mUiThread.execute(() -> mLocal.onScrollCaptureEnd(() -> { /* ignore */ }));
+            final ScrollCaptureCallback callback = mLocal;
+            mUiThread.execute(() -> callback.onScrollCaptureEnd(() -> { /* ignore */ }));
             mActive = false;
         }
         mActive = false;
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 21f75d4..2c81e89 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -35,6 +35,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Bitmap;
 import android.graphics.ColorSpace;
+import android.graphics.GraphicBuffer;
 import android.graphics.Matrix;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
@@ -188,6 +189,10 @@
             IBinder displayToken, int mode);
     private static native void nativeReparent(long transactionObj, long nativeObject,
             long newParentNativeObject);
+    private static native void nativeSetBuffer(long transactionObj, long nativeObject,
+            GraphicBuffer buffer);
+    private static native void nativeSetColorSpace(long transactionObj, long nativeObject,
+            int colorSpace);
 
     private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes);
 
@@ -3362,6 +3367,31 @@
             return this;
         }
 
+        /**
+         * Set a buffer for a SurfaceControl. This can only be used for SurfaceControls that were
+         * created as type {@link #FX_SURFACE_BLAST}
+         *
+         * @hide
+         */
+        public Transaction setBuffer(SurfaceControl sc, GraphicBuffer buffer) {
+            checkPreconditions(sc);
+            nativeSetBuffer(mNativeObject, sc.mNativeObject, buffer);
+            return this;
+        }
+
+        /**
+         * Set the color space for the SurfaceControl. The supported color spaces are SRGB
+         * and Display P3, other color spaces will be treated as SRGB. This can only be used for
+         * SurfaceControls that were created as type {@link #FX_SURFACE_BLAST}
+         *
+         * @hide
+         */
+        public Transaction setColorSpace(SurfaceControl sc, ColorSpace colorSpace) {
+            checkPreconditions(sc);
+            nativeSetColorSpace(mNativeObject, sc.mNativeObject, colorSpace.getId());
+            return this;
+        }
+
          /**
          * Merge the other transaction into this transaction, clearing the
          * other transaction as if it had been applied.
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 2b96a14..7bdf5cf 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -30,6 +30,7 @@
 import android.graphics.BlendMode;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.HardwareRenderer;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
@@ -221,8 +222,35 @@
 
     private int mPendingReportDraws;
 
-    private SurfaceControl.Transaction mRtTransaction = new SurfaceControl.Transaction();
-    private SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
+    /**
+     * Transaction that should be used from the render thread. This transaction is only thread safe
+     * with other calls directly from the render thread.
+     */
+    private final SurfaceControl.Transaction mRtTransaction = new SurfaceControl.Transaction();
+
+    /**
+     * Transaction that should be used whe
+     * {@link HardwareRenderer.FrameDrawingCallback#onFrameDraw} is invoked. All
+     * frame callbacks can use the same transaction since they will be thread safe
+     */
+    private final SurfaceControl.Transaction mFrameCallbackTransaction =
+            new SurfaceControl.Transaction();
+
+    /**
+     * Transaction that should be used for
+     * {@link RenderNode.PositionUpdateListener#positionChanged(long, int, int, int, int)}
+     * The callback is invoked from a thread pool so it's not thread safe with other render thread
+     * transactions. Keep the transactions for position changed callbacks on its own transaction.
+     */
+    private final SurfaceControl.Transaction mPositionChangedTransaction =
+            new SurfaceControl.Transaction();
+
+    /**
+     * A temporary transaction holder that should only be used when applying right away. There
+     * should be no assumption about thread safety for this transaction.
+     */
+    private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
+
     private int mParentSurfaceSequenceId;
 
     private RemoteAccessibilityController mRemoteAccessibilityController =
@@ -432,7 +460,6 @@
                  * This gets called on a RenderThread worker thread, so members accessed here must
                  * be protected by a lock.
                  */
-                final boolean useBLAST = useBLASTSync(viewRoot);
                 viewRoot.registerRtFrameCallback(frame -> {
                     try {
                         synchronized (mSurfaceControlLock) {
@@ -456,8 +483,9 @@
                                 Log.d(TAG, System.identityHashCode(this)
                                         + " updateSurfaceAlpha RT: set alpha=" + alpha);
                             }
-                            mRtTransaction.setAlpha(mSurfaceControl, alpha);
-                            applyRtTransaction(frame);
+
+                            mFrameCallbackTransaction.setAlpha(mSurfaceControl, alpha);
+                            applyOrMergeTransaction(mFrameCallbackTransaction, frame);
                         }
                         // It's possible that mSurfaceControl is released in the UI thread before
                         // the transaction completes. If that happens, an exception is thrown, which
@@ -806,7 +834,6 @@
          * This gets called on a RenderThread worker thread, so members accessed here must
          * be protected by a lock.
          */
-        final boolean useBLAST = useBLASTSync(viewRoot);
         viewRoot.registerRtFrameCallback(frame -> {
             try {
                 synchronized (mSurfaceControlLock) {
@@ -814,8 +841,8 @@
                         return;
                     }
 
-                    updateRelativeZ(mRtTransaction);
-                    applyRtTransaction(frame);
+                    updateRelativeZ(mFrameCallbackTransaction);
+                    applyOrMergeTransaction(mFrameCallbackTransaction, frame);
                 }
                 // It's possible that mSurfaceControl is released in the UI thread before
                 // the transaction completes. If that happens, an exception is thrown, which
@@ -1380,22 +1407,21 @@
         return mRTLastReportedPosition;
     }
 
-    private void setParentSpaceRectangle(Rect position, long frameNumber) {
+    private void setParentSpaceRectangle(Rect position, long frameNumber, Transaction t) {
         final ViewRootImpl viewRoot = getViewRootImpl();
-        applySurfaceTransforms(mSurfaceControl, mRtTransaction, position);
-        applyChildSurfaceTransaction_renderWorker(mRtTransaction, viewRoot.mSurface, frameNumber);
-        applyRtTransaction(frameNumber);
+        applySurfaceTransforms(mSurfaceControl, t, position);
+        applyChildSurfaceTransaction_renderWorker(t, viewRoot.mSurface, frameNumber);
+        applyOrMergeTransaction(t, frameNumber);
     }
 
-    private void applyRtTransaction(long frameNumber) {
+    private void applyOrMergeTransaction(Transaction t, long frameNumber) {
         final ViewRootImpl viewRoot = getViewRootImpl();
         boolean useBLAST = viewRoot != null && useBLASTSync(viewRoot);
         if (useBLAST) {
             // If we are using BLAST, merge the transaction with the viewroot buffer transaction.
-            viewRoot.mergeWithNextTransaction(mRtTransaction, frameNumber);
-            return;
+            viewRoot.mergeWithNextTransaction(t, frameNumber);
         } else {
-            mRtTransaction.apply();
+            t.apply();
         }
     }
 
@@ -1436,7 +1462,8 @@
                             left, top, right, bottom));
                 }
                 mRTLastReportedPosition.set(left, top, right, bottom);
-                setParentSpaceRectangle(mRTLastReportedPosition, frameNumber);
+                setParentSpaceRectangle(mRTLastReportedPosition, frameNumber,
+                        mPositionChangedTransaction);
                 // Now overwrite mRTLastReportedPosition with our values
             } catch (Exception ex) {
                 Log.e(TAG, "Exception from repositionChild", ex);
@@ -1448,7 +1475,7 @@
                 float bottom, float vecX, float vecY, float maxStretch) {
             mRtTransaction.setStretchEffect(mSurfaceControl, left, top, right, bottom, vecX, vecY,
                     maxStretch);
-            applyRtTransaction(frameNumber);
+            applyOrMergeTransaction(mRtTransaction, frameNumber);
         }
 
         @Override
@@ -1468,14 +1495,12 @@
              * need to hold the lock here.
              */
             synchronized (mSurfaceControlLock) {
-                final ViewRootImpl viewRoot = getViewRootImpl();
-
                 mRtTransaction.hide(mSurfaceControl);
                 if (mRtReleaseSurfaces) {
                     mRtReleaseSurfaces = false;
                     releaseSurfaces(mRtTransaction);
                 }
-                applyRtTransaction(frameNumber);
+                applyOrMergeTransaction(mRtTransaction, frameNumber);
                 mRtHandlingPositionUpdates = false;
             }
         }
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
index 88584f4..d84f571 100644
--- a/core/java/android/window/WindowContextController.java
+++ b/core/java/android/window/WindowContextController.java
@@ -105,6 +105,7 @@
      * a {@link com.android.server.wm.DisplayArea} by
      * {@link #attachToDisplayArea(int, int, Bundle)}.
      *
+     * @see WindowProviderService#attachToWindowToken(IBinder))
      * @see IWindowManager#attachWindowContextToWindowToken(IBinder, IBinder)
      */
     public void attachToWindowToken(IBinder windowToken) {
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
new file mode 100644
index 0000000..b8619fb
--- /dev/null
+++ b/core/java/android/window/WindowProviderService.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.annotation.UiContext;
+import android.app.ActivityThread;
+import android.app.LoadedApk;
+import android.app.Service;
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.view.Display;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams.WindowType;
+import android.view.WindowManagerImpl;
+
+// TODO(b/159767464): handle #onConfigurationChanged(Configuration)
+/**
+ * A {@link Service} responsible for showing a non-activity window, such as software keyboards or
+ * accessibility overlay windows. This {@link Service} has similar behavior to
+ * {@link WindowContext}, but is represented as {@link Service}.
+ *
+ * @see android.inputmethodservice.InputMethodService
+ * @see android.accessibilityservice.AccessibilityService
+ *
+ * @hide
+ */
+@TestApi
+@UiContext
+public abstract class WindowProviderService extends Service {
+
+    private final WindowTokenClient mWindowToken = new WindowTokenClient();
+    private final WindowContextController mController = new WindowContextController(mWindowToken);
+    private WindowManager mWindowManager;
+
+    /**
+     * Returns the type of this {@link WindowProviderService}.
+     * Each inheriting class must implement this method to provide the type of the window. It is
+     * used similar to {@code type} of {@link Context#createWindowContext(int, Bundle)}
+     *
+     * @see Context#createWindowContext(int, Bundle)
+     *
+     * @hide
+     */
+    @TestApi
+    @SuppressLint("OnNameExpected")
+    // Suppress the lint because it is not a callback and users should provide window type
+    // so we cannot make it final.
+    public abstract @WindowType int getWindowType();
+
+    /**
+     * Returns the option of this {@link WindowProviderService}.
+     * Default is {@code null}. The inheriting class can implement this method to provide the
+     * customization {@code option} of the window. It is used similar to {@code options} of
+     * {@link Context#createWindowContext(int, Bundle)}
+     *
+     * @see Context#createWindowContext(int, Bundle)
+     *
+     * @hide
+     */
+    @TestApi
+    @SuppressLint({"OnNameExpected", "NullableCollection"})
+    // Suppress the lint because it is not a callback and users may override this API to provide
+    // launch option. Also, the return value of this API is null by default.
+    @Nullable
+    public Bundle getWindowContextOptions() {
+        return null;
+    }
+
+    /**
+     * Attaches this WindowProviderService to the {@code windowToken}.
+     *
+     * @hide
+     */
+    @TestApi
+    public final void attachToWindowToken(@NonNull IBinder windowToken) {
+        mController.attachToWindowToken(windowToken);
+    }
+
+    /** @hide */
+    @Override
+    public final Context createServiceBaseContext(ActivityThread mainThread,
+            LoadedApk packageInfo) {
+        final Context context = super.createServiceBaseContext(mainThread, packageInfo);
+        // Always associate with the default display at initialization.
+        final Display defaultDisplay = context.getSystemService(DisplayManager.class)
+                .getDisplay(DEFAULT_DISPLAY);
+        return context.createTokenContext(mWindowToken, defaultDisplay);
+    }
+
+    @CallSuper
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mWindowToken.attachContext(this);
+        mController.attachToDisplayArea(getWindowType(), getDisplayId(), getWindowContextOptions());
+        mWindowManager = WindowManagerImpl.createWindowContextWindowManager(this);
+    }
+
+    @SuppressLint("OnNameExpected")
+    @Override
+    // Suppress the lint because ths is overridden from Context.
+    public @Nullable Object getSystemService(@NonNull String name) {
+        if (WINDOW_SERVICE.equals(name)) {
+            return mWindowManager;
+        }
+        return super.getSystemService(name);
+    }
+
+    @CallSuper
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mController.detachIfNeeded();
+    }
+}
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index e438d39..a0a0f32 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -92,7 +92,7 @@
     void notifyRegistrationFailed(int slotIndex, int subId, in CellIdentity cellIdentity,
             String chosenPlmn, int domain, int causeCode, int additionalCauseCode);
     void notifyBarringInfoChanged(int slotIndex, int subId, in BarringInfo barringInfo);
-    void notifyPhysicalChannelConfigForSubscriber(in int subId,
+    void notifyPhysicalChannelConfigForSubscriber(in int phoneId, in int subId,
             in List<PhysicalChannelConfig> configs);
     void notifyDataEnabled(in int phoneId, int subId, boolean enabled, int reason);
     void notifyAllowedNetworkTypesChanged(in int phoneId, in int subId, in int reason, in long allowedNetworkType);
diff --git a/core/java/com/android/internal/util/State.java b/core/java/com/android/internal/util/State.java
index 4613dad..d5c0f60 100644
--- a/core/java/com/android/internal/util/State.java
+++ b/core/java/com/android/internal/util/State.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.util;
 
+import android.annotation.SuppressLint;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Message;
@@ -25,6 +26,7 @@
  *
  * The class for implementing states in a StateMachine
  */
+@SuppressLint("AndroidFrameworkRequiresPermission")
 public class State implements IState {
 
     /**
diff --git a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
index a41511b..8aa2d57 100644
--- a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
+++ b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
@@ -17,6 +17,7 @@
 package com.android.internal.view;
 
 import android.annotation.UiThread;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.graphics.HardwareRenderer;
@@ -26,6 +27,7 @@
 import android.graphics.RectF;
 import android.graphics.RenderNode;
 import android.os.CancellationSignal;
+import android.provider.Settings;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.Display.ColorMode;
@@ -53,11 +55,14 @@
 
     private static final String TAG = "ScrollCaptureViewSupport";
 
-    private static final boolean WAIT_FOR_ANIMATION = true;
+    private static final String SETTING_CAPTURE_DELAY = "screenshot.scroll_capture_delay";
+    private static final long SETTING_CAPTURE_DELAY_DEFAULT = 60L; // millis
 
     private final WeakReference<V> mWeakView;
     private final ScrollCaptureViewHelper<V> mViewHelper;
     private final ViewRenderer mRenderer;
+    private final long mPostScrollDelayMillis;
+
     private boolean mStarted;
     private boolean mEnded;
 
@@ -66,6 +71,10 @@
         mRenderer = new ViewRenderer();
         // TODO(b/177649144): provide access to color space from android.media.Image
         mViewHelper = viewHelper;
+        Context context = containingView.getContext();
+        ContentResolver contentResolver = context.getContentResolver();
+        mPostScrollDelayMillis = Settings.Global.getLong(contentResolver,
+                SETTING_CAPTURE_DELAY, SETTING_CAPTURE_DELAY_DEFAULT);
     }
 
     /** Based on ViewRootImpl#updateColorModeIfNeeded */
@@ -120,37 +129,41 @@
     public final void onScrollCaptureImageRequest(ScrollCaptureSession session,
             CancellationSignal signal, Rect requestRect, Consumer<Rect> onComplete) {
         if (signal.isCanceled()) {
+            Log.w(TAG, "onScrollCaptureImageRequest: cancelled!");
             return;
         }
+
         V view = mWeakView.get();
         if (view == null || !view.isVisibleToUser()) {
             // Signal to the controller that we have a problem and can't continue.
             onComplete.accept(new Rect());
             return;
         }
+
         // Ask the view to scroll as needed to bring this area into view.
         ScrollResult scrollResult = mViewHelper.onScrollRequested(view, session.getScrollBounds(),
                 requestRect);
+
         if (scrollResult.availableArea.isEmpty()) {
             onComplete.accept(scrollResult.availableArea);
             return;
         }
-        view.invalidate(); // don't wait for vsync
 
         // For image capture, shift back by scrollDelta to arrive at the location within the view
         // where the requested content will be drawn
         Rect viewCaptureArea = new Rect(scrollResult.availableArea);
         viewCaptureArea.offset(0, -scrollResult.scrollDelta);
 
-        if (WAIT_FOR_ANIMATION) {
-            view.postOnAnimation(() ->  {
+        Runnable captureAction = () -> {
+            if (signal.isCanceled()) {
+                Log.w(TAG, "onScrollCaptureImageRequest: cancelled! skipping render.");
+            } else {
                 mRenderer.renderView(view, viewCaptureArea);
                 onComplete.accept(new Rect(scrollResult.availableArea));
-            });
-        } else {
-            mRenderer.renderView(view, viewCaptureArea);
-            onComplete.accept(new Rect(scrollResult.availableArea));
-        }
+            }
+        };
+
+        view.postOnAnimationDelayed(captureAction, mPostScrollDelayMillis);
     }
 
     @Override
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index ffba628..4194acb 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -30,6 +30,7 @@
 #include <android/hardware/display/IDeviceProductInfoConstants.h>
 #include <android/os/IInputConstants.h>
 #include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/android_graphics_GraphicBuffer.h>
 #include <android_runtime/android_hardware_HardwareBuffer.h>
 #include <android_runtime/android_view_Surface.h>
 #include <android_runtime/android_view_SurfaceSession.h>
@@ -253,6 +254,15 @@
     }
 }
 
+constexpr ui::Dataspace fromNamedColorSpaceValueToDataspace(const jint colorSpace) {
+    switch (colorSpace) {
+        case JNamedColorSpace::DISPLAY_P3:
+            return ui::Dataspace::DISPLAY_P3;
+        default:
+            return ui::Dataspace::V0_SRGB;
+    }
+}
+
 constexpr ui::Dataspace pickDataspaceFromColorMode(const ui::ColorMode colorMode) {
     switch (colorMode) {
         case ui::ColorMode::DISPLAY_P3:
@@ -553,6 +563,23 @@
     transaction->setGeometry(ctrl, source, dst, orientation);
 }
 
+static void nativeSetBuffer(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
+                            jobject bufferObject) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+    sp<GraphicBuffer> buffer(
+            android_graphics_GraphicBuffer_getNativeGraphicsBuffer(env, bufferObject));
+    transaction->setBuffer(ctrl, buffer);
+}
+
+static void nativeSetColorSpace(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
+                                jint colorSpace) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+    ui::Dataspace dataspace = fromNamedColorSpaceValueToDataspace(colorSpace);
+    transaction->setDataspace(ctrl, dataspace);
+}
+
 static void nativeSetBlurRegions(JNIEnv* env, jclass clazz, jlong transactionObj,
                                  jlong nativeObject, jobjectArray regions, jint regionsLength) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -1877,6 +1904,10 @@
             (void*)nativeGetDisplayedContentSample },
     {"nativeSetGeometry", "(JJLandroid/graphics/Rect;Landroid/graphics/Rect;J)V",
             (void*)nativeSetGeometry },
+    {"nativeSetBuffer", "(JJLandroid/graphics/GraphicBuffer;)V",
+            (void*)nativeSetBuffer },
+    {"nativeSetColorSpace", "(JJI)V",
+            (void*)nativeSetColorSpace },
     {"nativeSyncInputWindows", "(J)V",
             (void*)nativeSyncInputWindows },
     {"nativeGetDisplayBrightnessSupport", "(Landroid/os/IBinder;)Z",
diff --git a/core/proto/android/internal/OWNERS b/core/proto/android/internal/OWNERS
new file mode 100644
index 0000000..24e24c2
--- /dev/null
+++ b/core/proto/android/internal/OWNERS
@@ -0,0 +1,2 @@
+# Binder
+per-file binder_latency.proto = file:/core/java/com/android/internal/os/BINDER_OWNERS
\ No newline at end of file
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index a7127ad..b157146 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -308,6 +308,7 @@
     optional float minimize_amount = 27;
     optional bool created_by_organizer = 28;
     optional string affinity = 29;
+    optional bool has_child_pip_activity = 30;
 }
 
 /* represents ActivityRecordProto */
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index c51b2d8..588ae79 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -826,7 +826,7 @@
             that created the task, and therefore there will only be one instance of this activity
             in a task. In constrast to the {@code singleTask} launch mode, this activity can be
             started in multiple instances in different tasks if the
-            {@code FLAG_ACTIVITY_MULTIPLE_TASK} is set.-->
+            {@code FLAG_ACTIVITY_MULTIPLE_TASK} or {@code FLAG_ACTIVITY_NEW_DOCUMENT} is set.-->
         <enum name="singleInstancePerTask" value="4" />
     </attr>
     <!-- Specify the orientation an activity should be run in.  If not
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 419f142..f24d663 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4700,6 +4700,11 @@
      -->
     <color name="config_letterboxBackgroundColor">#000</color>
 
+    <!-- Horizonal position of a center of the letterboxed app window.
+        0 corresponds to the left side of the screen and 1 to the right side. If given value < 0
+        or > 1, it is ignored and central positionis used (0.5). -->
+    <item name="config_letterboxHorizontalPositionMultiplier" format="float" type="dimen">0.5</item>
+
     <!-- If true, hide the display cutout with display area -->
     <bool name="config_hideDisplayCutoutWithDisplayArea">false</bool>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d69c7f8..5715fab 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4186,6 +4186,7 @@
   <java-symbol type="dimen" name="config_letterboxBackgroundWallaperDarkScrimAlpha" />
   <java-symbol type="integer" name="config_letterboxBackgroundType" />
   <java-symbol type="color" name="config_letterboxBackgroundColor" />
+  <java-symbol type="dimen" name="config_letterboxHorizontalPositionMultiplier" />
 
   <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
 
diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
index fb0dd46..b2b9ab3 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
@@ -93,8 +93,8 @@
 
     @Test
     public void testComputeForPassword_metrics() {
-        final PasswordMetrics metrics =
-                PasswordMetrics.computeForPassword("6B~0z1Z3*8A".getBytes());
+        final PasswordMetrics metrics = PasswordMetrics.computeForPasswordOrPin(
+                "6B~0z1Z3*8A".getBytes(), /* isPin */ false);
         assertEquals(11, metrics.length);
         assertEquals(4, metrics.letters);
         assertEquals(3, metrics.upperCase);
@@ -133,61 +133,71 @@
     @Test
     public void testDetermineComplexity_lowNumeric() {
         assertEquals(PASSWORD_COMPLEXITY_LOW,
-                PasswordMetrics.computeForPassword("1234".getBytes()).determineComplexity());
+                PasswordMetrics.computeForPasswordOrPin("1234".getBytes(),
+                        /* isPin */true).determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_lowNumericComplex() {
         assertEquals(PASSWORD_COMPLEXITY_LOW,
-                PasswordMetrics.computeForPassword("124".getBytes()).determineComplexity());
+                PasswordMetrics.computeForPasswordOrPin("124".getBytes(),
+                        /* isPin */ true).determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_lowAlphabetic() {
         assertEquals(PASSWORD_COMPLEXITY_LOW,
-                PasswordMetrics.computeForPassword("a!".getBytes()).determineComplexity());
+                PasswordMetrics.computeForPasswordOrPin("a!".getBytes(),
+                        /* isPin */ false).determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_lowAlphanumeric() {
         assertEquals(PASSWORD_COMPLEXITY_LOW,
-                PasswordMetrics.computeForPassword("a!1".getBytes()).determineComplexity());
+                PasswordMetrics.computeForPasswordOrPin("a!1".getBytes(),
+                        /* isPin */ false).determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_mediumNumericComplex() {
         assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
-                PasswordMetrics.computeForPassword("1238".getBytes()).determineComplexity());
+                PasswordMetrics.computeForPasswordOrPin("1238".getBytes(),
+                        /* isPin */ true).determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_mediumAlphabetic() {
         assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
-                PasswordMetrics.computeForPassword("ab!c".getBytes()).determineComplexity());
+                PasswordMetrics.computeForPasswordOrPin("ab!c".getBytes(),
+                        /* isPin */ false).determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_mediumAlphanumeric() {
         assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
-                PasswordMetrics.computeForPassword("ab!1".getBytes()).determineComplexity());
+                PasswordMetrics.computeForPasswordOrPin("ab!1".getBytes(),
+                        /* isPin */ false).determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_highNumericComplex() {
         assertEquals(PASSWORD_COMPLEXITY_HIGH,
-                PasswordMetrics.computeForPassword("12389647!".getBytes()).determineComplexity());
+                PasswordMetrics.computeForPasswordOrPin("12389647!".getBytes(),
+                        /* isPin */ true).determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_highAlphabetic() {
         assertEquals(PASSWORD_COMPLEXITY_HIGH,
-                PasswordMetrics.computeForPassword("alphabetic!".getBytes()).determineComplexity());
+                PasswordMetrics.computeForPasswordOrPin("alphabetic!".getBytes(),
+                        /* isPin */ false).determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_highAlphanumeric() {
-        assertEquals(PASSWORD_COMPLEXITY_HIGH, PasswordMetrics.computeForPassword(
-                "alphanumeric123!".getBytes()).determineComplexity());
+        assertEquals(PASSWORD_COMPLEXITY_HIGH,
+                PasswordMetrics.computeForPasswordOrPin("alphanumeric123!".getBytes(),
+                        /* isPin */ false).determineComplexity());
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java b/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java
index e951054..f1be173 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java
@@ -28,6 +28,7 @@
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
 
 import static org.junit.Assert.assertEquals;
 
@@ -80,7 +81,7 @@
     public void testGetMinMetrics_numeric() {
         PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_NUMERIC);
         PasswordMetrics minMetrics = policy.getMinMetrics();
-        assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+        assertEquals(CREDENTIAL_TYPE_PIN, minMetrics.credType);
         assertEquals(TEST_VALUE, minMetrics.length);
         assertEquals(0, minMetrics.numeric); // numeric can doesn't really require digits.
         assertEquals(0, minMetrics.letters);
@@ -104,7 +105,7 @@
     public void testGetMinMetrics_numericComplex() {
         PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_NUMERIC_COMPLEX);
         PasswordMetrics minMetrics = policy.getMinMetrics();
-        assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+        assertEquals(CREDENTIAL_TYPE_PIN, minMetrics.credType);
         assertEquals(TEST_VALUE, minMetrics.length);
         assertEquals(0, minMetrics.numeric);
         assertEquals(0, minMetrics.letters);
diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
index 56c685a..3d18337 100644
--- a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
@@ -20,6 +20,7 @@
 
 import static org.testng.Assert.expectThrows;
 
+import android.app.appsearch.exceptions.AppSearchException;
 import android.content.Context;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -89,8 +90,12 @@
                 });
 
         // Verify the NullPointException has been thrown.
-        ExecutionException executionException = expectThrows(ExecutionException.class,
-                putDocumentsFuture::get);
-        assertThat(executionException.getCause()).isInstanceOf(NullPointerException.class);
+        ExecutionException executionException =
+                expectThrows(ExecutionException.class, putDocumentsFuture::get);
+        assertThat(executionException.getCause()).isInstanceOf(AppSearchException.class);
+        AppSearchException appSearchException = (AppSearchException) executionException.getCause();
+        assertThat(appSearchException.getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_INTERNAL_ERROR);
+        assertThat(appSearchException.getMessage()).startsWith("NullPointerException");
     }
 }
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchResultTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchResultTest.java
new file mode 100644
index 0000000..de0670b
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchResultTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import org.junit.Test;
+
+public class AppSearchResultTest {
+    @Test
+    public void testMapNullPointerException() {
+        NullPointerException e =
+                expectThrows(
+                        NullPointerException.class,
+                        () -> {
+                            Object o = null;
+                            o.toString();
+                        });
+        AppSearchResult<?> result = AppSearchResult.throwableToFailedResult(e);
+        assertThat(result.getResultCode()).isEqualTo(AppSearchResult.RESULT_INTERNAL_ERROR);
+        // Makes sure the exception name is included in the string. Some exceptions have terse or
+        // missing strings so it's confusing to read the output without the exception name.
+        assertThat(result.getErrorMessage()).startsWith("NullPointerException");
+    }
+}
diff --git a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
index 22c71b52..e7b88c8 100644
--- a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
+++ b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
@@ -32,6 +32,7 @@
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.ICancellationSignal;
+import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
@@ -189,4 +190,15 @@
         verifyNoMoreInteractions(mRemote);
     }
 
+    @Test
+    public void testClose_whileActive() throws RemoteException {
+        mConnection.startCapture(mSurface, mRemote);
+
+        mCallback.completeStartRequest();
+        assertTrue(mConnection.isActive());
+
+        mConnection.close();
+        mCallback.completeEndRequest();
+        assertFalse(mConnection.isActive());
+    }
 }
diff --git a/core/tests/coretests/src/android/window/WindowContextTest.java b/core/tests/coretests/src/android/window/WindowContextTest.java
index 614e7c1..83280f1 100644
--- a/core/tests/coretests/src/android/window/WindowContextTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextTest.java
@@ -17,6 +17,7 @@
 package android.window;
 
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 
@@ -209,6 +210,38 @@
         mWms.removeWindowToken(existingToken, DEFAULT_DISPLAY);
     }
 
+    @Test
+    public void testWindowContextAddViewWithSubWindowType_NotCrash() throws Throwable {
+        final WindowContext windowContext = createWindowContext(TYPE_INPUT_METHOD);
+        final WindowManager wm = windowContext.getSystemService(WindowManager.class);
+
+        // Create a WindowToken with system window type.
+        final IBinder existingToken = new Binder();
+        mWms.addWindowToken(existingToken, TYPE_INPUT_METHOD, windowContext.getDisplayId(),
+                null /* options */);
+
+        final WindowManager.LayoutParams params =
+                new WindowManager.LayoutParams(TYPE_INPUT_METHOD);
+        params.token = existingToken;
+        final View parentWindow = new View(windowContext);
+
+        final AttachStateListener listener = new AttachStateListener();
+        parentWindow.addOnAttachStateChangeListener(listener);
+
+        // Add the parent window
+        mInstrumentation.runOnMainSync(() -> wm.addView(parentWindow, params));
+
+        assertTrue(listener.mLatch.await(4, TimeUnit.SECONDS));
+
+        final WindowManager.LayoutParams subWindowAttrs =
+                new WindowManager.LayoutParams(TYPE_APPLICATION_ATTACHED_DIALOG);
+        subWindowAttrs.token = parentWindow.getWindowToken();
+        final View subWindow = new View(windowContext);
+
+        // Add a window with sub-window type.
+        mInstrumentation.runOnMainSync(() -> wm.addView(subWindow, subWindowAttrs));
+    }
+
     private WindowContext createWindowContext() {
         return createWindowContext(TYPE_APPLICATION_OVERLAY);
     }
@@ -219,4 +252,16 @@
                 .getDisplay(DEFAULT_DISPLAY);
         return (WindowContext) instContext.createWindowContext(display, type,  null /* options */);
     }
+
+    private static class AttachStateListener implements View.OnAttachStateChangeListener {
+        final CountDownLatch mLatch = new CountDownLatch(1);
+
+        @Override
+        public void onViewAttachedToWindow(View v) {
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onViewDetachedFromWindow(View v) {}
+    }
 }
diff --git a/core/tests/nfctests/Android.bp b/core/tests/nfctests/Android.bp
new file mode 100644
index 0000000..335cea1
--- /dev/null
+++ b/core/tests/nfctests/Android.bp
@@ -0,0 +1,38 @@
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "NfcManagerTests",
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "mockito-target-minus-junit4",
+    ],
+    libs: [
+        "android.test.runner",
+    ],
+    srcs: ["src/**/*.java"],
+    platform_apis: true,
+    certificate: "platform",
+    test_suites: ["device-tests"],
+}
diff --git a/core/tests/nfctests/AndroidManifest.xml b/core/tests/nfctests/AndroidManifest.xml
new file mode 100644
index 0000000..99e2c34c
--- /dev/null
+++ b/core/tests/nfctests/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.nfc">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <!-- This is a self-instrumenting test package. -->
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.nfc"
+                     android:label="NFC Manager Tests">
+    </instrumentation>
+
+</manifest>
+
diff --git a/core/tests/nfctests/AndroidTest.xml b/core/tests/nfctests/AndroidTest.xml
new file mode 100644
index 0000000..490d6f5
--- /dev/null
+++ b/core/tests/nfctests/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for NFC Manager test cases">
+    <option name="test-suite-tag" value="apct"/>
+    <option name="test-suite-tag" value="apct-instrumentation"/>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="NfcManagerTests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct"/>
+    <option name="test-tag" value="NfcManagerTests"/>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.nfc" />
+        <option name="hidden-api-checks" value="false"/>
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
+    </test>
+</configuration>
diff --git a/core/tests/nfctests/OWNERS b/core/tests/nfctests/OWNERS
new file mode 100644
index 0000000..34b095c
--- /dev/null
+++ b/core/tests/nfctests/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/nfc/OWNERS
diff --git a/core/tests/nfctests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java b/core/tests/nfctests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java
new file mode 100644
index 0000000..43f9b6f
--- /dev/null
+++ b/core/tests/nfctests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.nfc.NfcAdapter.ControllerAlwaysOnListener;
+import android.os.RemoteException;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Test of {@link NfcControllerAlwaysOnListener}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NfcControllerAlwaysOnListenerTest {
+
+    private INfcAdapter mNfcAdapter = mock(INfcAdapter.class);
+
+    private Throwable mThrowRemoteException = new RemoteException("RemoteException");
+
+    private static Executor getExecutor() {
+        return new Executor() {
+            @Override
+            public void execute(Runnable command) {
+                command.run();
+            }
+        };
+    }
+
+    private static void verifyListenerInvoked(ControllerAlwaysOnListener listener) {
+        verify(listener, times(1)).onControllerAlwaysOnChanged(anyBoolean());
+    }
+
+    @Test
+    public void testRegister_RegisterUnregister() throws RemoteException {
+        NfcControllerAlwaysOnListener mListener =
+                new NfcControllerAlwaysOnListener(mNfcAdapter);
+        ControllerAlwaysOnListener mockListener1 = mock(ControllerAlwaysOnListener.class);
+        ControllerAlwaysOnListener mockListener2 = mock(ControllerAlwaysOnListener.class);
+
+        // Verify that the state listener registered with the NFC Adapter
+        mListener.register(getExecutor(), mockListener1);
+        verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+
+        // Register a second client and no new call to NFC Adapter
+        mListener.register(getExecutor(), mockListener2);
+        verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+
+        // Unregister first listener
+        mListener.unregister(mockListener1);
+        verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+        verify(mNfcAdapter, times(0)).unregisterControllerAlwaysOnListener(any());
+
+        // Unregister second listener and the state listener registered with the NFC Adapter
+        mListener.unregister(mockListener2);
+        verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+        verify(mNfcAdapter, times(1)).unregisterControllerAlwaysOnListener(any());
+    }
+
+    @Test
+    public void testRegister_FirstRegisterFails() throws RemoteException {
+        NfcControllerAlwaysOnListener mListener =
+                new NfcControllerAlwaysOnListener(mNfcAdapter);
+        ControllerAlwaysOnListener mockListener1 = mock(ControllerAlwaysOnListener.class);
+        ControllerAlwaysOnListener mockListener2 = mock(ControllerAlwaysOnListener.class);
+
+        // Throw a remote exception whenever first registering
+        doThrow(mThrowRemoteException).when(mNfcAdapter).registerControllerAlwaysOnListener(
+                any());
+
+        mListener.register(getExecutor(), mockListener1);
+        verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+
+        // No longer throw an exception, instead succeed
+        doNothing().when(mNfcAdapter).registerControllerAlwaysOnListener(any());
+
+        // Register a different listener
+        mListener.register(getExecutor(), mockListener2);
+        verify(mNfcAdapter, times(2)).registerControllerAlwaysOnListener(any());
+
+        // Ensure first and second listener were invoked
+        mListener.onControllerAlwaysOnChanged(true);
+        verifyListenerInvoked(mockListener1);
+        verifyListenerInvoked(mockListener2);
+    }
+
+    @Test
+    public void testRegister_RegisterSameListenerTwice() throws RemoteException {
+        NfcControllerAlwaysOnListener mListener =
+                new NfcControllerAlwaysOnListener(mNfcAdapter);
+        ControllerAlwaysOnListener mockListener = mock(ControllerAlwaysOnListener.class);
+
+        // Register the same listener Twice
+        mListener.register(getExecutor(), mockListener);
+        mListener.register(getExecutor(), mockListener);
+        verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+
+        // Invoke a state change and ensure the listener is only called once
+        mListener.onControllerAlwaysOnChanged(true);
+        verifyListenerInvoked(mockListener);
+    }
+
+    @Test
+    public void testNotify_AllListenersNotified() throws RemoteException {
+
+        NfcControllerAlwaysOnListener listener = new NfcControllerAlwaysOnListener(mNfcAdapter);
+        List<ControllerAlwaysOnListener> mockListeners = new ArrayList<>();
+        for (int i = 0; i < 10; i++) {
+            ControllerAlwaysOnListener mockListener = mock(ControllerAlwaysOnListener.class);
+            listener.register(getExecutor(), mockListener);
+            mockListeners.add(mockListener);
+        }
+
+        // Invoke a state change and ensure all listeners are invoked
+        listener.onControllerAlwaysOnChanged(true);
+        for (ControllerAlwaysOnListener mListener : mockListeners) {
+            verifyListenerInvoked(mListener);
+        }
+    }
+
+    @Test
+    public void testStateChange_CorrectValue() {
+        runStateChangeValue(true, true);
+        runStateChangeValue(false, false);
+
+    }
+
+    private void runStateChangeValue(boolean isEnabledIn, boolean isEnabledOut) {
+        NfcControllerAlwaysOnListener listener = new NfcControllerAlwaysOnListener(mNfcAdapter);
+        ControllerAlwaysOnListener mockListener = mock(ControllerAlwaysOnListener.class);
+        listener.register(getExecutor(), mockListener);
+        listener.onControllerAlwaysOnChanged(isEnabledIn);
+        verify(mockListener, times(1)).onControllerAlwaysOnChanged(isEnabledOut);
+        verify(mockListener, times(0)).onControllerAlwaysOnChanged(!isEnabledOut);
+    }
+}
diff --git a/errorprone/java/android/annotation/SuppressLint.java b/errorprone/java/android/annotation/SuppressLint.java
new file mode 100644
index 0000000..2d3456b
--- /dev/null
+++ b/errorprone/java/android/annotation/SuppressLint.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Indicates that Lint should ignore the specified warnings for the annotated element. */
+@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
+@Retention(RetentionPolicy.CLASS)
+public @interface SuppressLint {
+    /**
+     * The set of warnings (identified by the lint issue id) that should be
+     * ignored by lint. It is not an error to specify an unrecognized name.
+     */
+    String[] value();
+}
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
new file mode 100644
index 0000000..3b5a58c
--- /dev/null
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.errorprone.bugpatterns.android;
+
+import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
+import static com.google.errorprone.matchers.Matchers.allOf;
+import static com.google.errorprone.matchers.Matchers.anyOf;
+import static com.google.errorprone.matchers.Matchers.enclosingClass;
+import static com.google.errorprone.matchers.Matchers.instanceMethod;
+import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
+import static com.google.errorprone.matchers.Matchers.methodInvocation;
+import static com.google.errorprone.matchers.Matchers.methodIsNamed;
+import static com.google.errorprone.matchers.Matchers.staticMethod;
+
+import android.annotation.SuppressLint;
+
+import com.google.auto.service.AutoService;
+import com.google.errorprone.BugPattern;
+import com.google.errorprone.VisitorState;
+import com.google.errorprone.bugpatterns.BugChecker;
+import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
+import com.google.errorprone.matchers.Description;
+import com.google.errorprone.matchers.Matcher;
+import com.google.errorprone.util.ASTHelpers;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.util.TreeScanner;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.Type.ClassType;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.ExecutableElement;
+
+/**
+ * Inspects both the client and server side of AIDL interfaces to ensure that
+ * any {@code RequiresPermission} annotations are consistently declared and
+ * enforced.
+ */
+@AutoService(BugChecker.class)
+@BugPattern(
+    name = "AndroidFrameworkRequiresPermission",
+    summary = "Verifies that @RequiresPermission annotations are consistent across AIDL",
+    severity = WARNING)
+public final class RequiresPermissionChecker extends BugChecker implements MethodTreeMatcher {
+    private static final String ANNOTATION_REQUIRES_PERMISSION = "RequiresPermission";
+
+    private static final Matcher<ExpressionTree> ENFORCE_VIA_CONTEXT = methodInvocation(
+            instanceMethod()
+                    .onDescendantOf("android.content.Context")
+                    .withNameMatching(
+                            Pattern.compile("^(enforce|check)(Calling)?(OrSelf)?Permission$")));
+    private static final Matcher<ExpressionTree> ENFORCE_VIA_CHECKER = methodInvocation(
+            staticMethod()
+                    .onClass("android.content.PermissionChecker")
+                    .withNameMatching(Pattern.compile("^check.*")));
+
+    private static final Matcher<MethodTree> BINDER_INTERNALS = allOf(
+            enclosingClass(isSubtypeOf("android.os.IInterface")),
+            anyOf(
+                    methodIsNamed("onTransact"),
+                    methodIsNamed("dump"),
+                    enclosingClass(simpleNameMatches(Pattern.compile("^(Stub|Default|Proxy)$")))));
+    private static final Matcher<MethodTree> LOCAL_INTERNALS = anyOf(
+            methodIsNamed("finalize"),
+            allOf(
+                    enclosingClass(isSubtypeOf("android.content.BroadcastReceiver")),
+                    methodIsNamed("onReceive")),
+            allOf(
+                    enclosingClass(isSubtypeOf("android.database.ContentObserver")),
+                    methodIsNamed("onChange")),
+            allOf(
+                    enclosingClass(isSubtypeOf("android.os.Handler")),
+                    methodIsNamed("handleMessage")),
+            allOf(
+                    enclosingClass(isSubtypeOf("android.os.IBinder.DeathRecipient")),
+                    methodIsNamed("binderDied")));
+
+    private static final Matcher<ExpressionTree> CLEAR_CALL = methodInvocation(staticMethod()
+            .onClass("android.os.Binder").withSignature("clearCallingIdentity()"));
+    private static final Matcher<ExpressionTree> RESTORE_CALL = methodInvocation(staticMethod()
+            .onClass("android.os.Binder").withSignature("restoreCallingIdentity(long)"));
+
+    @Override
+    public Description matchMethod(MethodTree tree, VisitorState state) {
+        // Ignore methods without an implementation
+        if (tree.getBody() == null) return Description.NO_MATCH;
+
+        // Ignore certain types of Binder generated code
+        if (BINDER_INTERNALS.matches(tree, state)) return Description.NO_MATCH;
+
+        // Ignore known-local methods which don't need to propagate
+        if (LOCAL_INTERNALS.matches(tree, state)) return Description.NO_MATCH;
+
+        // Ignore when suppressed via superclass
+        final MethodSymbol method = ASTHelpers.getSymbol(tree);
+        if (isSuppressedRecursively(method, state)) return Description.NO_MATCH;
+
+        // First, look at all outgoing method invocations to ensure that we
+        // carry those annotations forward; yell if we're too narrow
+        final ParsedRequiresPermission expectedPerm = parseRequiresPermissionRecursively(
+                method, state);
+        final ParsedRequiresPermission actualPerm = new ParsedRequiresPermission();
+        final Description desc = tree.accept(new TreeScanner<Description, Void>() {
+            private boolean clearedCallingIdentity = false;
+
+            @Override
+            public Description visitMethodInvocation(MethodInvocationTree node, Void param) {
+                if (CLEAR_CALL.matches(node, state)) {
+                    clearedCallingIdentity = true;
+                } else if (RESTORE_CALL.matches(node, state)) {
+                    clearedCallingIdentity = false;
+                } else if (!clearedCallingIdentity) {
+                    final ParsedRequiresPermission nodePerm = parseRequiresPermissionRecursively(
+                            node, state);
+                    if (!expectedPerm.containsAll(nodePerm)) {
+                        return buildDescription(node).setMessage("Annotated " + expectedPerm
+                                + " but too narrow; invokes method requiring " + nodePerm).build();
+                    } else {
+                        actualPerm.addAll(nodePerm);
+                    }
+                }
+                return super.visitMethodInvocation(node, param);
+            }
+
+            @Override
+            public Description reduce(Description r1, Description r2) {
+                return (r1 != null) ? r1 : r2;
+            }
+        }, null);
+        if (desc != null) return desc;
+
+        // Second, determine if we actually used all permissions that we claim
+        // to require; yell if we're too broad
+        if (!actualPerm.containsAll(expectedPerm)) {
+            return buildDescription(tree).setMessage("Annotated " + expectedPerm
+                    + " but too wide; only invokes methods requiring " + actualPerm).build();
+        }
+
+        return Description.NO_MATCH;
+    }
+
+    static class ParsedRequiresPermission {
+        final Set<String> allOf = new HashSet<>();
+        final Set<String> anyOf = new HashSet<>();
+
+        public boolean isEmpty() {
+            return allOf.isEmpty() && anyOf.isEmpty();
+        }
+
+        /**
+         * Validate that this annotation effectively "contains" the given
+         * annotation. This is typically used to ensure that a method carries
+         * along all relevant annotations for the methods it invokes.
+         */
+        public boolean containsAll(ParsedRequiresPermission perm) {
+            boolean allMet = allOf.containsAll(perm.allOf);
+            boolean anyMet = false;
+            if (perm.anyOf.isEmpty()) {
+                anyMet = true;
+            } else {
+                for (String anyPerm : perm.anyOf) {
+                    if (allOf.contains(anyPerm) || anyOf.contains(anyPerm)) {
+                        anyMet = true;
+                    }
+                }
+            }
+            return allMet && anyMet;
+        }
+
+        @Override
+        public String toString() {
+            if (isEmpty()) {
+                return "[none]";
+            }
+            String res = "{allOf=" + allOf;
+            if (!anyOf.isEmpty()) {
+                res += " anyOf=" + anyOf;
+            }
+            res += "}";
+            return res;
+        }
+
+        public void addAll(ParsedRequiresPermission perm) {
+            this.allOf.addAll(perm.allOf);
+            this.anyOf.addAll(perm.anyOf);
+        }
+
+        public void addAll(AnnotationMirror a) {
+            for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : a
+                    .getElementValues().entrySet()) {
+                if (entry.getKey().getSimpleName().contentEquals("value")) {
+                    maybeAdd(allOf, entry.getValue());
+                } else if (entry.getKey().getSimpleName().contentEquals("allOf")) {
+                    maybeAdd(allOf, entry.getValue());
+                } else if (entry.getKey().getSimpleName().contentEquals("anyOf")) {
+                    maybeAdd(anyOf, entry.getValue());
+                }
+            }
+        }
+
+        private static void maybeAdd(Set<String> set, Object value) {
+            if (value instanceof AnnotationValue) {
+                maybeAdd(set, ((AnnotationValue) value).getValue());
+            } else if (value instanceof String) {
+                set.add((String) value);
+            } else if (value instanceof Collection) {
+                for (Object o : (Collection) value) {
+                    maybeAdd(set, o);
+                }
+            } else {
+                throw new RuntimeException(String.valueOf(value.getClass()));
+            }
+        }
+    }
+
+    private static ParsedRequiresPermission parseRequiresPermissionRecursively(
+            MethodInvocationTree tree, VisitorState state) {
+        if (ENFORCE_VIA_CONTEXT.matches(tree, state)) {
+            final ParsedRequiresPermission res = new ParsedRequiresPermission();
+            res.allOf.add(String.valueOf(ASTHelpers.constValue(tree.getArguments().get(0))));
+            return res;
+        } else if (ENFORCE_VIA_CHECKER.matches(tree, state)) {
+            final ParsedRequiresPermission res = new ParsedRequiresPermission();
+            res.allOf.add(String.valueOf(ASTHelpers.constValue(tree.getArguments().get(1))));
+            return res;
+        } else {
+            final MethodSymbol method = ASTHelpers.getSymbol(tree);
+            return parseRequiresPermissionRecursively(method, state);
+        }
+    }
+
+    /**
+     * Parse any {@code RequiresPermission} annotations associated with the
+     * given method, defined either directly on the method or by any superclass.
+     */
+    private static ParsedRequiresPermission parseRequiresPermissionRecursively(
+            MethodSymbol method, VisitorState state) {
+        final List<MethodSymbol> symbols = new ArrayList<>();
+        symbols.add(method);
+        symbols.addAll(ASTHelpers.findSuperMethods(method, state.getTypes()));
+
+        final ParsedRequiresPermission res = new ParsedRequiresPermission();
+        for (MethodSymbol symbol : symbols) {
+            for (AnnotationMirror a : symbol.getAnnotationMirrors()) {
+                if (a.getAnnotationType().asElement().getSimpleName()
+                        .contentEquals(ANNOTATION_REQUIRES_PERMISSION)) {
+                    res.addAll(a);
+                }
+            }
+        }
+        return res;
+    }
+
+    private boolean isSuppressedRecursively(MethodSymbol method, VisitorState state) {
+        // Is method suppressed anywhere?
+        if (isSuppressed(method)) return true;
+        for (MethodSymbol symbol : ASTHelpers.findSuperMethods(method, state.getTypes())) {
+            if (isSuppressed(symbol)) return true;
+        }
+
+        // Is class suppressed anywhere?
+        final ClassSymbol clazz = ASTHelpers.enclosingClass(method);
+        if (isSuppressed(clazz)) return true;
+        Type type = clazz.getSuperclass();
+        while (type != null) {
+            if (isSuppressed(type.tsym)) return true;
+            if (type instanceof ClassType) {
+                type = ((ClassType) type).supertype_field;
+            } else {
+                type = null;
+            }
+        }
+        return false;
+    }
+
+    public boolean isSuppressed(Symbol symbol) {
+        return isSuppressed(ASTHelpers.getAnnotation(symbol, SuppressWarnings.class))
+                || isSuppressed(ASTHelpers.getAnnotation(symbol, SuppressLint.class));
+    }
+
+    private boolean isSuppressed(SuppressWarnings anno) {
+        return (anno != null) && !Collections.disjoint(Arrays.asList(anno.value()), allNames());
+    }
+
+    private boolean isSuppressed(SuppressLint anno) {
+        return (anno != null) && !Collections.disjoint(Arrays.asList(anno.value()), allNames());
+    }
+
+    private static Matcher<ClassTree> simpleNameMatches(Pattern pattern) {
+        return new Matcher<ClassTree>() {
+            @Override
+            public boolean matches(ClassTree tree, VisitorState state) {
+                final CharSequence name = tree.getSimpleName().toString();
+                return pattern.matcher(name).matches();
+            }
+        };
+    }
+}
diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java
new file mode 100644
index 0000000..771258d
--- /dev/null
+++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.errorprone.bugpatterns.android;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.errorprone.CompilationTestHelper;
+import com.google.errorprone.bugpatterns.android.RequiresPermissionChecker.ParsedRequiresPermission;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+@RunWith(JUnit4.class)
+public class RequiresPermissionCheckerTest {
+    private CompilationTestHelper compilationHelper;
+
+    private static final String RED = "red";
+    private static final String BLUE = "blue";
+
+    @Before
+    public void setUp() {
+        compilationHelper = CompilationTestHelper.newInstance(
+                RequiresPermissionChecker.class, getClass());
+    }
+
+    private static ParsedRequiresPermission build(Collection<String> allOf,
+            Collection<String> anyOf) {
+        ParsedRequiresPermission res = new ParsedRequiresPermission();
+        res.allOf.addAll(allOf);
+        res.anyOf.addAll(anyOf);
+        return res;
+    }
+
+    @Test
+    public void testParser_AllOf() {
+        final ParsedRequiresPermission a = build(Arrays.asList(RED, BLUE), Arrays.asList());
+        final ParsedRequiresPermission b = build(Arrays.asList(RED), Arrays.asList());
+        assertTrue(a.containsAll(b));
+        assertFalse(b.containsAll(a));
+    }
+
+    @Test
+    public void testParser_AnyOf() {
+        final ParsedRequiresPermission a = build(Arrays.asList(), Arrays.asList(RED, BLUE));
+        final ParsedRequiresPermission b = build(Arrays.asList(), Arrays.asList(RED));
+        assertTrue(a.containsAll(b));
+        assertTrue(b.containsAll(a));
+    }
+
+    @Test
+    public void testParser_AnyOf_AllOf() {
+        final ParsedRequiresPermission a = build(Arrays.asList(RED, BLUE), Arrays.asList());
+        final ParsedRequiresPermission b = build(Arrays.asList(), Arrays.asList(RED));
+        assertTrue(a.containsAll(b));
+        assertFalse(b.containsAll(a));
+    }
+
+    @Test
+    public void testSimple() {
+        compilationHelper
+                .addSourceFile("/android/annotation/RequiresPermission.java")
+                .addSourceFile("/android/content/Context.java")
+                .addSourceLines("ColorManager.java",
+                        "import android.annotation.RequiresPermission;",
+                        "import android.content.Context;",
+                        "public abstract class ColorManager extends Context {",
+                        "  private static final String RED = \"red\";",
+                        "  private static final String BLUE = \"blue\";",
+                        "  @RequiresPermission(RED) abstract int red();",
+                        "  @RequiresPermission(BLUE) abstract int blue();",
+                        "  @RequiresPermission(allOf={RED, BLUE}) abstract int all();",
+                        "  @RequiresPermission(anyOf={RED, BLUE}) abstract int any();",
+                        "  @RequiresPermission(allOf={RED, BLUE})",
+                        "  int redPlusBlue() { return red() + blue(); }",
+                        "  @RequiresPermission(allOf={RED, BLUE})",
+                        "  int allPlusRed() { return all() + red(); }",
+                        "  @RequiresPermission(allOf={RED})",
+                        "  int anyPlusRed() { return any() + red(); }",
+                        "}")
+                .doTest();
+    }
+
+    @Test
+    public void testManager() {
+        compilationHelper
+                .addSourceFile("/android/annotation/RequiresPermission.java")
+                .addSourceFile("/android/foo/IColorService.java")
+                .addSourceFile("/android/os/IInterface.java")
+                .addSourceLines("ColorManager.java",
+                        "import android.annotation.RequiresPermission;",
+                        "import android.foo.IColorService;",
+                        "public class ColorManager {",
+                        "  IColorService mService;",
+                        "  @RequiresPermission(IColorService.RED)",
+                        "  void redValid() {",
+                        "    mService.red();",
+                        "  }",
+                        "  @RequiresPermission(allOf={IColorService.RED, IColorService.BLUE})",
+                        "  // BUG: Diagnostic contains:",
+                        "  void redOverbroad() {",
+                        "    mService.red();",
+                        "  }",
+                        "  @RequiresPermission(IColorService.BLUE)",
+                        "  void redInvalid() {",
+                        "    // BUG: Diagnostic contains:",
+                        "    mService.red();",
+                        "  }",
+                        "  void redMissing() {",
+                        "    // BUG: Diagnostic contains:",
+                        "    mService.red();",
+                        "  }",
+                        "}")
+                .doTest();
+    }
+
+    @Test
+    public void testService() {
+        compilationHelper
+                .addSourceFile("/android/annotation/RequiresPermission.java")
+                .addSourceFile("/android/content/Context.java")
+                .addSourceFile("/android/foo/IColorService.java")
+                .addSourceFile("/android/os/IInterface.java")
+                .addSourceLines("ColorService.java",
+                        "import android.annotation.RequiresPermission;",
+                        "import android.content.Context;",
+                        "import android.foo.IColorService;",
+                        "class ColorService extends Context implements IColorService {",
+                        "  public void none() {}",
+                        "  // BUG: Diagnostic contains:",
+                        "  public void red() {}",
+                        "  // BUG: Diagnostic contains:",
+                        "  public void redAndBlue() {}",
+                        "  // BUG: Diagnostic contains:",
+                        "  public void redOrBlue() {}",
+                        "  void onTransact(int code) {",
+                        "    red();",
+                        "  }",
+                        "}",
+                        "class ValidService extends ColorService {",
+                        "  public void red() {",
+                        "    ((Context) this).enforceCallingOrSelfPermission(RED, null);",
+                        "  }",
+                        "}",
+                        "class InvalidService extends ColorService {",
+                        "  public void red() {",
+                        "    // BUG: Diagnostic contains:",
+                        "    ((Context) this).enforceCallingOrSelfPermission(BLUE, null);",
+                        "  }",
+                        "}",
+                        "class NestedService extends ColorService {",
+                        "  public void red() {",
+                        "    enforceRed();",
+                        "  }",
+                        "  @RequiresPermission(RED)",
+                        "  public void enforceRed() {",
+                        "    ((Context) this).enforceCallingOrSelfPermission(RED, null);",
+                        "  }",
+                        "}")
+                .doTest();
+    }
+
+    @Test
+    public void testBroadcastReceiver() {
+        compilationHelper
+                .addSourceFile("/android/annotation/RequiresPermission.java")
+                .addSourceFile("/android/content/BroadcastReceiver.java")
+                .addSourceFile("/android/content/Context.java")
+                .addSourceFile("/android/content/Intent.java")
+                .addSourceLines("ColorManager.java",
+                        "import android.annotation.RequiresPermission;",
+                        "import android.content.BroadcastReceiver;",
+                        "import android.content.Context;",
+                        "import android.content.Intent;",
+                        "public abstract class ColorManager extends BroadcastReceiver {",
+                        "  private static final String RED = \"red\";",
+                        "  @RequiresPermission(RED) abstract int red();",
+                        "  // BUG: Diagnostic contains:",
+                        "  public void onSend() { red(); }",
+                        "  public void onReceive(Context context, Intent intent) { red(); }",
+                        "}")
+                .doTest();
+    }
+
+    @Test
+    @Ignore
+    public void testContentObserver() {
+        compilationHelper
+                .addSourceFile("/android/annotation/RequiresPermission.java")
+                .addSourceFile("/android/database/ContentObserver.java")
+                .addSourceLines("ColorManager.java",
+                        "import android.annotation.RequiresPermission;",
+                        "import android.database.ContentObserver;",
+                        "public abstract class ColorManager {",
+                        "  private static final String RED = \"red\";",
+                        "  @RequiresPermission(RED) abstract int red();",
+                        "  public void example() {",
+                        "    ContentObserver ob = new ContentObserver() {",
+                        "      public void onChange(boolean selfChange) {",
+                        "        red();",
+                        "      }",
+                        "    };",
+                        "  }",
+                        "}")
+                .doTest();
+    }
+
+    @Test
+    public void testHandler() {
+        compilationHelper
+                .addSourceFile("/android/annotation/RequiresPermission.java")
+                .addSourceFile("/android/os/Handler.java")
+                .addSourceFile("/android/os/Message.java")
+                .addSourceLines("ColorManager.java",
+                        "import android.annotation.RequiresPermission;",
+                        "import android.os.Handler;",
+                        "import android.os.Message;",
+                        "public abstract class ColorManager extends Handler {",
+                        "  private static final String RED = \"red\";",
+                        "  @RequiresPermission(RED) abstract int red();",
+                        "  // BUG: Diagnostic contains:",
+                        "  public void sendMessage() { red(); }",
+                        "  public void handleMessage(Message msg) { red(); }",
+                        "}")
+                .doTest();
+    }
+
+    @Test
+    public void testDeathRecipient() {
+        compilationHelper
+                .addSourceFile("/android/annotation/RequiresPermission.java")
+                .addSourceFile("/android/os/IBinder.java")
+                .addSourceLines("ColorManager.java",
+                        "import android.annotation.RequiresPermission;",
+                        "import android.os.IBinder;",
+                        "public abstract class ColorManager implements IBinder.DeathRecipient {",
+                        "  private static final String RED = \"red\";",
+                        "  @RequiresPermission(RED) abstract int red();",
+                        "  // BUG: Diagnostic contains:",
+                        "  public void binderAlive() { red(); }",
+                        "  public void binderDied() { red(); }",
+                        "}")
+                .doTest();
+    }
+
+    @Test
+    public void testClearCallingIdentity() {
+        compilationHelper
+                .addSourceFile("/android/annotation/RequiresPermission.java")
+                .addSourceFile("/android/os/Binder.java")
+                .addSourceLines("ColorManager.java",
+                        "import android.annotation.RequiresPermission;",
+                        "import android.os.Binder;",
+                        "public abstract class ColorManager {",
+                        "  private static final String RED = \"red\";",
+                        "  private static final String BLUE = \"blue\";",
+                        "  @RequiresPermission(RED) abstract int red();",
+                        "  @RequiresPermission(BLUE) abstract int blue();",
+                        "  @RequiresPermission(BLUE)",
+                        "  public void half() {",
+                        "    final long token = Binder.clearCallingIdentity();",
+                        "    try {",
+                        "      red();",
+                        "    } finally {",
+                        "      Binder.restoreCallingIdentity(token);",
+                        "    }",
+                        "    blue();",
+                        "  }",
+                        "  public void full() {",
+                        "    final long token = Binder.clearCallingIdentity();",
+                        "    red();",
+                        "    blue();",
+                        "  }",
+                        "  @RequiresPermission(allOf={RED, BLUE})",
+                        "  public void none() {",
+                        "    red();",
+                        "    blue();",
+                        "    final long token = Binder.clearCallingIdentity();",
+                        "  }",
+                        "}")
+                .doTest();
+    }
+
+    @Test
+    public void testSuppressLint() {
+        compilationHelper
+                .addSourceFile("/android/annotation/RequiresPermission.java")
+                .addSourceFile("/android/annotation/SuppressLint.java")
+                .addSourceLines("Example.java",
+                        "import android.annotation.RequiresPermission;",
+                        "import android.annotation.SuppressLint;",
+                        "@SuppressLint(\"AndroidFrameworkRequiresPermission\")",
+                        "abstract class Parent {",
+                        "  private static final String RED = \"red\";",
+                        "  @RequiresPermission(RED) abstract int red();",
+                        "}",
+                        "abstract class Child extends Parent {",
+                        "  private static final String BLUE = \"blue\";",
+                        "  @RequiresPermission(BLUE) abstract int blue();",
+                        "  public void toParent() { red(); }",
+                        "  public void toSibling() { blue(); }",
+                        "}")
+                .doTest();
+    }
+}
diff --git a/errorprone/tests/res/android/annotation/RequiresPermission.java b/errorprone/tests/res/android/annotation/RequiresPermission.java
new file mode 100644
index 0000000..670eb3b
--- /dev/null
+++ b/errorprone/tests/res/android/annotation/RequiresPermission.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(SOURCE)
+@Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER})
+public @interface RequiresPermission {
+    String value() default "";
+    String[] allOf() default {};
+    String[] anyOf() default {};
+}
diff --git a/errorprone/tests/res/android/annotation/SuppressLint.java b/errorprone/tests/res/android/annotation/SuppressLint.java
new file mode 100644
index 0000000..4150c47
--- /dev/null
+++ b/errorprone/tests/res/android/annotation/SuppressLint.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
+@Retention(RetentionPolicy.CLASS)
+public @interface SuppressLint {
+    String[] value();
+}
diff --git a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl b/errorprone/tests/res/android/content/BroadcastReceiver.java
similarity index 61%
copy from core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
copy to errorprone/tests/res/android/content/BroadcastReceiver.java
index 1e4fdd7..9d066b7 100644
--- a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
+++ b/errorprone/tests/res/android/content/BroadcastReceiver.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,16 +14,10 @@
  * limitations under the License.
  */
 
-package android.nfc;
+package android.content;
 
-/**
- * @hide
- */
-oneway interface INfcControllerAlwaysOnStateCallback {
-  /**
-   * Called whenever the controller always on state changes
-   *
-   * @param isEnabled true if the state is enabled, false otherwise
-   */
-  void onControllerAlwaysOnStateChanged(boolean isEnabled);
+public class BroadcastReceiver {
+    public void onReceive(Context context, Intent intent) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/errorprone/tests/res/android/content/Context.java b/errorprone/tests/res/android/content/Context.java
index 7ba3fbb..323b8dd 100644
--- a/errorprone/tests/res/android/content/Context.java
+++ b/errorprone/tests/res/android/content/Context.java
@@ -20,4 +20,7 @@
     public int getUserId() {
         return 0;
     }
+
+    public void enforceCallingOrSelfPermission(String permission, String message) {
+    }
 }
diff --git a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl b/errorprone/tests/res/android/database/ContentObserver.java
similarity index 61%
copy from core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
copy to errorprone/tests/res/android/database/ContentObserver.java
index 1e4fdd7..4c73a10 100644
--- a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
+++ b/errorprone/tests/res/android/database/ContentObserver.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,16 +14,10 @@
  * limitations under the License.
  */
 
-package android.nfc;
+package android.database;
 
-/**
- * @hide
- */
-oneway interface INfcControllerAlwaysOnStateCallback {
-  /**
-   * Called whenever the controller always on state changes
-   *
-   * @param isEnabled true if the state is enabled, false otherwise
-   */
-  void onControllerAlwaysOnStateChanged(boolean isEnabled);
+public abstract class ContentObserver {
+    public void onChange(boolean selfChange) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/errorprone/tests/res/android/foo/IColorService.java b/errorprone/tests/res/android/foo/IColorService.java
new file mode 100644
index 0000000..20c8e95
--- /dev/null
+++ b/errorprone/tests/res/android/foo/IColorService.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.foo;
+
+import android.annotation.RequiresPermission;
+
+public interface IColorService extends android.os.IInterface {
+    public static final String RED = "red";
+    public static final String BLUE = "blue";
+
+    public void none();
+    @RequiresPermission(RED)
+    public void red();
+    @RequiresPermission(allOf = { RED, BLUE })
+    public void redAndBlue();
+    @RequiresPermission(anyOf = { RED, BLUE })
+    public void redOrBlue();
+}
diff --git a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl b/errorprone/tests/res/android/os/Handler.java
similarity index 61%
copy from core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
copy to errorprone/tests/res/android/os/Handler.java
index 1e4fdd7..f001896 100644
--- a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
+++ b/errorprone/tests/res/android/os/Handler.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,16 +14,10 @@
  * limitations under the License.
  */
 
-package android.nfc;
+package android.os;
 
-/**
- * @hide
- */
-oneway interface INfcControllerAlwaysOnStateCallback {
-  /**
-   * Called whenever the controller always on state changes
-   *
-   * @param isEnabled true if the state is enabled, false otherwise
-   */
-  void onControllerAlwaysOnStateChanged(boolean isEnabled);
+public class Handler {
+    public void handleMessage(Message msg) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl b/errorprone/tests/res/android/os/IBinder.java
similarity index 61%
copy from core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
copy to errorprone/tests/res/android/os/IBinder.java
index 1e4fdd7..214a396 100644
--- a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
+++ b/errorprone/tests/res/android/os/IBinder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,16 +14,10 @@
  * limitations under the License.
  */
 
-package android.nfc;
+package android.os;
 
-/**
- * @hide
- */
-oneway interface INfcControllerAlwaysOnStateCallback {
-  /**
-   * Called whenever the controller always on state changes
-   *
-   * @param isEnabled true if the state is enabled, false otherwise
-   */
-  void onControllerAlwaysOnStateChanged(boolean isEnabled);
+public interface IBinder {
+    public interface DeathRecipient {
+        public void binderDied();
+    }
 }
diff --git a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl b/errorprone/tests/res/android/os/Message.java
similarity index 61%
copy from core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
copy to errorprone/tests/res/android/os/Message.java
index 1e4fdd7..2421263 100644
--- a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
+++ b/errorprone/tests/res/android/os/Message.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,16 +14,7 @@
  * limitations under the License.
  */
 
-package android.nfc;
+package android.os;
 
-/**
- * @hide
- */
-oneway interface INfcControllerAlwaysOnStateCallback {
-  /**
-   * Called whenever the controller always on state changes
-   *
-   * @param isEnabled true if the state is enabled, false otherwise
-   */
-  void onControllerAlwaysOnStateChanged(boolean isEnabled);
+public class Message {
 }
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 6698a01..a138fee 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -40,7 +40,7 @@
     <integer name="long_press_dock_anim_duration">250</integer>
 
     <!-- Animation duration for translating of one handed when trigger / dismiss. -->
-    <integer name="config_one_handed_translate_animation_duration">300</integer>
+    <integer name="config_one_handed_translate_animation_duration">800</integer>
 
     <!-- One handed mode default offset % of display size -->
     <fraction name="config_one_handed_offset">40%</fraction>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
index 125e322..25dd3ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
@@ -22,8 +22,7 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.view.SurfaceControl;
-import android.view.animation.Interpolator;
-import android.view.animation.OvershootInterpolator;
+import android.view.animation.BaseInterpolator;
 import android.window.WindowContainerToken;
 
 import androidx.annotation.VisibleForTesting;
@@ -54,7 +53,7 @@
     public @interface TransitionDirection {
     }
 
-    private final Interpolator mOvershootInterpolator;
+    private final OneHandedInterpolator mInterpolator;
     private final OneHandedSurfaceTransactionHelper mSurfaceTransactionHelper;
     private final HashMap<WindowContainerToken, OneHandedTransitionAnimator> mAnimatorMap =
             new HashMap<>();
@@ -64,7 +63,7 @@
      */
     public OneHandedAnimationController(Context context) {
         mSurfaceTransactionHelper = new OneHandedSurfaceTransactionHelper(context);
-        mOvershootInterpolator = new OvershootInterpolator();
+        mInterpolator = new OneHandedInterpolator();
     }
 
     @SuppressWarnings("unchecked")
@@ -102,7 +101,7 @@
     OneHandedTransitionAnimator setupOneHandedTransitionAnimator(
             OneHandedTransitionAnimator animator) {
         animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper);
-        animator.setInterpolator(mOvershootInterpolator);
+        animator.setInterpolator(mInterpolator);
         animator.setFloatValues(FRACTION_START, FRACTION_END);
         return animator;
     }
@@ -112,6 +111,8 @@
      *
      * @param <T> Type of property to animate, either offset (float)
      */
+    // TODO: Refactoring to use SpringAnimation and DynamicAnimation instead of using ValueAnimator
+    //  to implement One-Handed transition animation. (b/185129031)
     public abstract static class OneHandedTransitionAnimator<T> extends ValueAnimator implements
             ValueAnimator.AnimatorUpdateListener,
             ValueAnimator.AnimatorListener {
@@ -297,4 +298,15 @@
             };
         }
     }
+
+    /**
+     * An Interpolator for One-Handed transition animation.
+     */
+    public class OneHandedInterpolator extends BaseInterpolator {
+        @Override
+        public float getInterpolation(float input) {
+            return (float) (Math.pow(2, -10 * input) * Math.sin(((input - 4.0f) / 4.0f)
+                    * (2.0f * Math.PI) / 4.0f) + 1);
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
index d4f229c..3af0ff0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
@@ -216,7 +216,7 @@
         }
 
         ArrayList<RemoteAction> mediaActions = new ArrayList<>();
-        boolean isPlaying = mMediaController.getPlaybackState().isActive();
+        boolean isPlaying = mMediaController.getPlaybackState().isActiveState();
         long actions = mMediaController.getPlaybackState().getActions();
 
         // Prev action
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index 71f533c..ab00dd5 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -24,6 +24,7 @@
 #include "SkClipStack.h"
 #include "SkRect.h"
 #include "SkM44.h"
+#include "utils/GLUtils.h"
 
 namespace android {
 namespace uirenderer {
@@ -170,6 +171,8 @@
         setScissor(info.height, clipRegion.getBounds());
     }
 
+    // WebView may swallow GL errors, so catch them here
+    GL_CHECKPOINT(LOW);
     mWebViewHandle->drawGl(info);
 
     if (clearStencilAfterFunctor) {
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 82814de..8ab9d22 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -60,12 +60,11 @@
     // Add the marker annotation to allow HWUI to determine where the current
     // clip/transformation should be applied
     SkVector vector = rect.getSimpleRadii();
-    const int dataSize = 2;
-    float data[dataSize];
+    float data[2];
     data[0] = vector.x();
     data[1] = vector.y();
     mRecorder.drawAnnotation(rect.rect(), HOLE_PUNCH_ANNOTATION.c_str(),
-                             SkData::MakeWithCopy(data, dataSize));
+                             SkData::MakeWithCopy(data, 2 * sizeof(float)));
 
     // Clear the current rect within the layer itself
     SkPaint paint = SkPaint();
diff --git a/libs/hwui/pipeline/skia/TransformCanvas.cpp b/libs/hwui/pipeline/skia/TransformCanvas.cpp
index 6bfbb0d..a6e4c4c 100644
--- a/libs/hwui/pipeline/skia/TransformCanvas.cpp
+++ b/libs/hwui/pipeline/skia/TransformCanvas.cpp
@@ -22,7 +22,7 @@
 
 void TransformCanvas::onDrawAnnotation(const SkRect& rect, const char* key, SkData* value) {
     if (HOLE_PUNCH_ANNOTATION == key) {
-        auto* rectParams = static_cast<const float*>(value->data());
+        auto* rectParams = reinterpret_cast<const float*>(value->data());
         float radiusX = rectParams[0];
         float radiusY = rectParams[1];
         SkRRect roundRect = SkRRect::MakeRectXY(rect, radiusX, radiusY);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 85d1bc5..4dc1cca 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -6887,7 +6887,6 @@
      *
      * @return true if successful, otherwise false
      */
-    @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS)
     public @EncodedSurroundOutputMode int getEncodedSurroundMode() {
         try {
             return getService().getEncodedSurroundMode(
@@ -6944,7 +6943,6 @@
      *
      * @return whether the required surround format is enabled
      */
-    @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS)
     public boolean isSurroundFormatEnabled(@AudioFormat.SurroundSoundEncoding int audioFormat) {
         try {
             return getService().isSurroundFormatEnabled(audioFormat);
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 9eacc74..e7d30ebb 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.LongDef;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -492,26 +493,15 @@
 
     /**
      * Returns whether this is considered as an active playback state.
-     * <p>
-     * The playback state is considered as an active if the state is one of the following:
-     * <ul>
-     * <li>{@link #STATE_BUFFERING}</li>
-     * <li>{@link #STATE_CONNECTING}</li>
-     * <li>{@link #STATE_FAST_FORWARDING}</li>
-     * <li>{@link #STATE_PLAYING}</li>
-     * <li>{@link #STATE_REWINDING}</li>
-     * <li>{@link #STATE_SKIPPING_TO_NEXT}</li>
-     * <li>{@link #STATE_SKIPPING_TO_PREVIOUS}</li>
-     * <li>{@link #STATE_SKIPPING_TO_QUEUE_ITEM}</li>
-     * </ul>
+     * @hide
      */
-    public boolean isActive() {
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public boolean isActiveState() {
         switch (mState) {
             case PlaybackState.STATE_FAST_FORWARDING:
             case PlaybackState.STATE_REWINDING:
             case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
             case PlaybackState.STATE_SKIPPING_TO_NEXT:
-            case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
             case PlaybackState.STATE_BUFFERING:
             case PlaybackState.STATE_CONNECTING:
             case PlaybackState.STATE_PLAYING:
diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp
index c62402d..bb93af9 100644
--- a/packages/Connectivity/framework/Android.bp
+++ b/packages/Connectivity/framework/Android.bp
@@ -100,6 +100,7 @@
         "//frameworks/base",
 
         // Tests using hidden APIs
+        "//cts/tests/netlegacy22.api",
         "//external/sl4a:__subpackages__",
         "//frameworks/base/tests/net:__subpackages__",
         "//frameworks/libs/net/common/testutils",
diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt
index 4719772..9e2cd3e 100644
--- a/packages/Connectivity/framework/api/module-lib-current.txt
+++ b/packages/Connectivity/framework/api/module-lib-current.txt
@@ -13,7 +13,7 @@
     method @NonNull public static String getPrivateDnsMode(@NonNull android.content.Context);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackAsUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
-    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
     method @Deprecated public boolean requestRouteToHostAddress(int, java.net.InetAddress);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptPartialConnectivity(@NonNull android.net.Network, boolean, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptUnvalidated(@NonNull android.net.Network, boolean, boolean);
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index c4a0d69..96f2de6 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -5362,10 +5362,10 @@
      * {@link #unregisterNetworkCallback(NetworkCallback)}.
      *
      * @param request {@link NetworkRequest} describing this request.
-     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
-     *                If null, the callback is invoked on the default internal Handler.
      * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
      *                        the callback must not be shared - it uniquely specifies this request.
+     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+     *                If null, the callback is invoked on the default internal Handler.
      * @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
      * @throws SecurityException if missing the appropriate permissions.
      * @throws RuntimeException if the app already has too many callbacks registered.
@@ -5380,7 +5380,8 @@
             NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
     })
     public void requestBackgroundNetwork(@NonNull NetworkRequest request,
-            @NonNull Handler handler, @NonNull NetworkCallback networkCallback) {
+            @NonNull NetworkCallback networkCallback,
+            @SuppressLint("ListenerLast") @NonNull Handler handler) {
         final NetworkCapabilities nc = request.networkCapabilities;
         sendRequestForNetwork(nc, networkCallback, 0, BACKGROUND_REQUEST,
                 TYPE_NONE, new CallbackHandler(handler));
diff --git a/packages/Connectivity/framework/src/android/net/LinkProperties.java b/packages/Connectivity/framework/src/android/net/LinkProperties.java
index e41ed72..99f48b4 100644
--- a/packages/Connectivity/framework/src/android/net/LinkProperties.java
+++ b/packages/Connectivity/framework/src/android/net/LinkProperties.java
@@ -686,8 +686,8 @@
     }
 
     /**
-     * Adds a {@link RouteInfo} to this {@code LinkProperties}, if a {@link RouteInfo}
-     * with the same {@link RouteInfo.RouteKey} with different properties
+     * Adds a {@link RouteInfo} to this {@code LinkProperties}. If there is a {@link RouteInfo}
+     * with the same destination, gateway and interface with different properties
      * (e.g., different MTU), it will be updated. If the {@link RouteInfo} had an
      * interface name set and that differs from the interface set for this
      * {@code LinkProperties} an {@link IllegalArgumentException} will be thrown.
diff --git a/packages/Connectivity/framework/src/android/net/NetworkProvider.java b/packages/Connectivity/framework/src/android/net/NetworkProvider.java
index cfb7325..0665af5 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkProvider.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkProvider.java
@@ -167,7 +167,15 @@
         ConnectivityManager.from(mContext).declareNetworkRequestUnfulfillable(request);
     }
 
-    /** @hide */
+    /**
+     * A callback for parties registering a NetworkOffer.
+     *
+     * This is used with {@link ConnectivityManager#offerNetwork}. When offering a network,
+     * the system will use this callback to inform the caller that a network corresponding to
+     * this offer is needed or unneeded.
+     *
+     * @hide
+     */
     @SystemApi
     public interface NetworkOfferCallback {
         /**
diff --git a/packages/Connectivity/framework/src/android/net/NetworkScore.java b/packages/Connectivity/framework/src/android/net/NetworkScore.java
index b0752e9..7be7deb 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkScore.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkScore.java
@@ -48,7 +48,14 @@
     })
     public @interface KeepConnectedReason { }
 
+    /**
+     * Do not keep this network connected if there is no outstanding request for it.
+     */
     public static final int KEEP_CONNECTED_NONE = 0;
+    /**
+     * Keep this network connected even if there is no outstanding request for it, because it
+     * is being considered for handover.
+     */
     public static final int KEEP_CONNECTED_FOR_HANDOVER = 1;
 
     // Agent-managed policies
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml
index a24456e..f57061a 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml
@@ -18,5 +18,5 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"વ્યવસ્થાપકે ચાલુ કરેલ"</string>
-    <string name="disabled_by_admin" msgid="4023569940620832713">"વ્યવસ્થાપકે બંધ કરેલ"</string>
+    <string name="disabled_by_admin" msgid="4023569940620832713">"વ્યવસ્થાપકે બંધ કરેલું"</string>
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml
index 199a2d6..bddf43c 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml
@@ -18,5 +18,5 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Attivata dall\'amministratore"</string>
-    <string name="disabled_by_admin" msgid="4023569940620832713">"Disattivata dall\'amministratore"</string>
+    <string name="disabled_by_admin" msgid="4023569940620832713">"Opzione disattivata dall\'amministratore"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 14ccd80..70b826a 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1014,6 +1014,9 @@
     <!-- Settings item title to select whether to show transcoding notifications. [CHAR LIMIT=85] -->
     <string name="transcode_notification">Show transcoding notifications</string>
 
+    <!-- Settings item title to select whether to disable cache for transcoding. [CHAR LIMIT=85] -->
+    <string name="transcode_disable_cache">Disable transcoding cache</string>
+
     <!-- Services settings screen, setting option name for the user to go to the screen to view running services -->
     <string name="runningservices_settings_title">Running services</string>
     <!-- Services settings screen, setting option summary for the user to go to the screen to view running services  -->
diff --git a/packages/SystemUI/res/drawable/ic_brightness.xml b/packages/SystemUI/res/drawable/ic_brightness.xml
index f443332..842af26 100644
--- a/packages/SystemUI/res/drawable/ic_brightness.xml
+++ b/packages/SystemUI/res/drawable/ic_brightness.xml
@@ -1,29 +1,23 @@
 <!--
-Copyright (C) 2020 The Android Open Source Project
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
 
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-
-    <path
-        android:pathData="M18,14.48V18h-3.52L12,20.48L9.52,18H6v-3.52L3.52,12L6,9.52V6h3.52L12,3.52L14.48,6H18v3.52L20.48,12L18,14.48z"
-    />
-
-    <path
-        android:pathData=" M20,8.69 V4h-4.69L12,0.69L8.69,4H4v4.69L0.69,12L4,15.31V20h4.69L12,23.31L15.31,20H20v-4.69L23.31,12L20,8.69z M18,14.48V18h-3.52L12,20.48L9.52,18H6v-3.52L3.52,12L6,9.52V6h3.52L12,3.52L14.48,6H18v3.52L20.48,12L18,14.48z M12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5s5,-2.24 5,-5S14.76,7 12,7z"
-        android:fillColor="#FFFFFF" />
-</vector>
+<level-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Levels in drawables go from 0 to 10000 -->
+    <!-- "One third" of the range per icon -->
+    <item android:maxLevel="3333" android:drawable="@drawable/ic_brightness_low" />
+    <item android:maxLevel="6666" android:drawable="@drawable/ic_brightness_medium" />
+    <item android:maxLevel="10000" android:drawable="@drawable/ic_brightness_full" />
+</level-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_brightness_thumb.xml b/packages/SystemUI/res/drawable/ic_brightness_full.xml
similarity index 86%
rename from packages/SystemUI/res/drawable/ic_brightness_thumb.xml
rename to packages/SystemUI/res/drawable/ic_brightness_full.xml
index d721988..f443332 100644
--- a/packages/SystemUI/res/drawable/ic_brightness_thumb.xml
+++ b/packages/SystemUI/res/drawable/ic_brightness_full.xml
@@ -1,5 +1,5 @@
 <!--
-Copyright (C) 2017 The Android Open Source Project
+Copyright (C) 2020 The Android Open Source Project
 
    Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
@@ -21,9 +21,9 @@
 
     <path
         android:pathData="M18,14.48V18h-3.52L12,20.48L9.52,18H6v-3.52L3.52,12L6,9.52V6h3.52L12,3.52L14.48,6H18v3.52L20.48,12L18,14.48z"
-        android:fillColor="?android:attr/colorBackgroundFloating" />
+    />
 
     <path
         android:pathData=" M20,8.69 V4h-4.69L12,0.69L8.69,4H4v4.69L0.69,12L4,15.31V20h4.69L12,23.31L15.31,20H20v-4.69L23.31,12L20,8.69z M18,14.48V18h-3.52L12,20.48L9.52,18H6v-3.52L3.52,12L6,9.52V6h3.52L12,3.52L14.48,6H18v3.52L20.48,12L18,14.48z M12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5s5,-2.24 5,-5S14.76,7 12,7z"
-        android:fillColor="?android:attr/colorControlActivated" />
+        android:fillColor="#FFFFFF" />
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_brightness_low.xml b/packages/SystemUI/res/drawable/ic_brightness_low.xml
new file mode 100644
index 0000000..b463556
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_brightness_low.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M20,8.69L20,4h-4.69L12,0.69 8.69,4L4,4v4.69L0.69,12 4,15.31L4,20h4.69L12,23.31 15.31,20L20,20v-4.69L23.31,12 20,8.69zM18,14.48L18,18h-3.52L12,20.48 9.52,18L6,18v-3.52L3.52,12 6,9.52L6,6h3.52L12,3.52 14.48,6L18,6v3.52L20.48,12 18,14.48zM12,9c1.65,0 3,1.35 3,3s-1.35,3 -3,3 -3,-1.35 -3,-3 1.35,-3 3,-3m0,-2c-2.76,0 -5,2.24 -5,5s2.24,5 5,5 5,-2.24 5,-5 -2.24,-5 -5,-5z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_brightness_medium.xml b/packages/SystemUI/res/drawable/ic_brightness_medium.xml
new file mode 100644
index 0000000..80acc4d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_brightness_medium.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M20,8.69L20,4h-4.69L12,0.69 8.69,4L4,4v4.69L0.69,12 4,15.31L4,20h4.69L12,23.31 15.31,20L20,20v-4.69L23.31,12 20,8.69zM18,14.48L18,18h-3.52L12,20.48 9.52,18L6,18v-3.52L3.52,12 6,9.52L6,6h3.52L12,3.52 14.48,6L18,6v3.52L20.48,12 18,14.48zM12,17c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5v10z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/notif_footer_btn_background.xml b/packages/SystemUI/res/drawable/notif_footer_btn_background.xml
index f35f5d1..6725a26 100644
--- a/packages/SystemUI/res/drawable/notif_footer_btn_background.xml
+++ b/packages/SystemUI/res/drawable/notif_footer_btn_background.xml
@@ -15,10 +15,12 @@
   ~ limitations under the License.
   -->
 
+
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:shape="rectangle">
     <solid
-        android:color="@color/notif_pill_background"
+        android:color="?androidprv:attr/colorSurface"
         />
     <corners android:radius="20dp" />
 
diff --git a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml b/packages/SystemUI/res/drawable/system_animation_ongoing_dot.xml
similarity index 65%
copy from packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
copy to packages/SystemUI/res/drawable/system_animation_ongoing_dot.xml
index b8ea622..4e9d380 100644
--- a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
+++ b/packages/SystemUI/res/drawable/system_animation_ongoing_dot.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
+<!-- Copyright (C) 2016 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -13,10 +13,14 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle" >
+
+<!-- dot drawable for ongoing system privacy events -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
     <solid
-        android:color="?android:attr/textColorSecondary" />
-    <corners android:radius="2dp" />
-</shape>
+        android:color="@color/privacy_circle"/>
+    <size
+        android:width="6dp"
+        android:height="6dp"
+        />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml
index dc9d920..bb82f91 100644
--- a/packages/SystemUI/res/layout/notification_snooze.xml
+++ b/packages/SystemUI/res/layout/notification_snooze.xml
@@ -17,10 +17,11 @@
 
 <com.android.systemui.statusbar.notification.row.NotificationSnooze
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical"
-    android:background="?android:attr/colorBackground"
+    android:background="?androidprv:attr/colorSurface"
     android:theme="@style/Theme.SystemUI">
 
     <RelativeLayout
@@ -34,7 +35,7 @@
             android:layout_height="wrap_content"
             android:layout_alignParentStart="true"
             android:layout_centerVertical="true"
-            android:paddingStart="@*android:dimen/notification_content_margin_start"
+            android:paddingStart="@*android:dimen/notification_content_margin_end"
             android:textColor="?android:attr/textColorPrimary"
             android:paddingEnd="4dp"/>
 
diff --git a/packages/SystemUI/res/layout/notification_snooze_option.xml b/packages/SystemUI/res/layout/notification_snooze_option.xml
index f203839..d42cc02 100644
--- a/packages/SystemUI/res/layout/notification_snooze_option.xml
+++ b/packages/SystemUI/res/layout/notification_snooze_option.xml
@@ -17,8 +17,8 @@
 <TextView
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
-        android:layout_height="48dp"
-        android:layout_marginStart="@*android:dimen/notification_content_margin_start"
+        android:layout_height="@*android:dimen/notification_headerless_min_height"
+        android:layout_marginStart="@*android:dimen/notification_content_margin_end"
         android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
         android:gravity="center_vertical"
         android:textSize="14sp"
diff --git a/packages/SystemUI/res/layout/people_space_initial_layout.xml b/packages/SystemUI/res/layout/people_space_initial_layout.xml
new file mode 100644
index 0000000..ec29d18
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_space_initial_layout.xml
@@ -0,0 +1,69 @@
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:theme="@android:style/Theme.DeviceDefault.DayNight"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:background="@drawable/people_space_tile_view_card"
+        android:id="@+id/item"
+        android:orientation="horizontal"
+        android:gravity="center"
+        android:layout_gravity="top"
+        android:paddingVertical="16dp"
+        android:paddingHorizontal="16dp"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <LinearLayout
+            android:orientation="vertical"
+            android:paddingEnd="20dp"
+            android:gravity="bottom"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content">
+
+            <ImageView
+                android:background="@drawable/ic_person"
+                android:layout_width="48dp"
+                android:layout_height="48dp" />
+
+            <TextView
+                android:id="@+id/name"
+                android:paddingTop="2dp"
+                android:text="@string/empty_user_name"
+                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+                android:textColor="?android:attr/textColorPrimary"
+                android:textSize="12sp"
+                android:maxLines="1"
+                android:ellipsize="end"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+        </LinearLayout>
+
+        <TextView
+            android:text="@string/status_before_loading"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+            android:textSize="12sp"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxLines="3"
+            android:ellipsize="end" />
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_space_placeholder_layout.xml b/packages/SystemUI/res/layout/people_space_placeholder_layout.xml
index b85d6b0..2402bd7 100644
--- a/packages/SystemUI/res/layout/people_space_placeholder_layout.xml
+++ b/packages/SystemUI/res/layout/people_space_placeholder_layout.xml
@@ -14,63 +14,56 @@
   ~ limitations under the License.
   -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:theme="@android:style/Theme.DeviceDefault.DayNight"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
+
     <LinearLayout
         android:background="@drawable/people_space_tile_view_card"
         android:id="@+id/item"
-        android:orientation="vertical"
+        android:orientation="horizontal"
+        android:gravity="center"
+        android:layout_gravity="top"
+        android:paddingVertical="16dp"
+        android:paddingHorizontal="16dp"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
 
         <LinearLayout
-            android:orientation="horizontal"
-            android:gravity="center"
-            android:paddingVertical="2dp"
-            android:paddingHorizontal="8dp"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent">
+            android:orientation="vertical"
+            android:paddingEnd="20dp"
+            android:gravity="bottom"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content">
+
             <ImageView
                 android:background="@drawable/ic_person"
-                android:id="@+id/person_icon_only"
-                android:layout_width="60dp"
-                android:layout_height="60dp"/>
+                android:layout_width="48dp"
+                android:layout_height="48dp" />
 
-            <LinearLayout
-                android:orientation="vertical"
-                android:paddingStart="8dp"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-
-                <ImageView
-                    android:id="@+id/availability"
-                    android:layout_width="10dp"
-                    android:layout_height="10dp"
-                    android:background="@drawable/circle_green_10dp"/>
-                <TextView
-                    android:id="@+id/name"
-                    android:text="@string/empty_user_name"
-                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                    android:textColor="?android:attr/textColorPrimary"
-                    android:textSize="14sp"
-                    android:maxLines="1"
-                    android:ellipsize="end"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"/>
-
-                <TextView
-                    android:id="@+id/last_interaction"
-                    android:text="@string/empty_status"
-                    android:textColor="?android:attr/textColorSecondary"
-                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                    android:textSize="12sp"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:maxLines="3"
-                    android:ellipsize="end"/>
-            </LinearLayout>
+            <TextView
+                android:id="@+id/name"
+                android:paddingTop="2dp"
+                android:text="@string/empty_user_name"
+                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+                android:textColor="?android:attr/textColorPrimary"
+                android:textSize="12sp"
+                android:maxLines="1"
+                android:ellipsize="end"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
         </LinearLayout>
+
+        <TextView
+            android:text="@string/empty_status"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+            android:textSize="12sp"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxLines="3"
+            android:ellipsize="end" />
     </LinearLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_customize_panel_content.xml b/packages/SystemUI/res/layout/qs_customize_panel_content.xml
index 7cce1ba..6a1be81 100644
--- a/packages/SystemUI/res/layout/qs_customize_panel_content.xml
+++ b/packages/SystemUI/res/layout/qs_customize_panel_content.xml
@@ -45,8 +45,6 @@
             android:layout_width="match_parent"
             android:layout_height="0dp"
             android:layout_weight="1"
-            android:paddingLeft="@dimen/qs_tile_layout_margin_side"
-            android:paddingRight="@dimen/qs_tile_layout_margin_side"
             android:paddingBottom="28dp"
             android:clipToPadding="false"
             android:scrollIndicators="top"
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 0217972..343b398 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-** Copyright 2012, The Android Open Source Project
+** Copyright 2021, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -16,8 +16,7 @@
 -->
 
 <!-- Extends FrameLayout -->
-<com.android.systemui.qs.QSFooterView
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.systemui.qs.QSFooterView xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/qs_footer"
     android:layout_width="match_parent"
     android:layout_height="@dimen/qs_footer_height"
@@ -32,77 +31,70 @@
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:layout_gravity="center_vertical"
-        android:gravity="end" >
+        android:orientation="vertical">
 
-        <com.android.keyguard.AlphaOptimizedLinearLayout
-            android:id="@+id/qs_footer_actions_edit_container"
-            android:layout_width="@integer/qs_footer_actions_width"
-            android:layout_height="match_parent"
-            android:layout_weight="@integer/qs_footer_actions_weight"
-            android:gravity="center_vertical|start" >
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="48dp"
+            android:layout_gravity="center_vertical">
+
+            <TextView
+                android:id="@+id/build"
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:paddingStart="@dimen/qs_tile_margin_horizontal"
+                android:paddingEnd="4dp"
+                android:layout_weight="1"
+                android:clickable="true"
+                android:ellipsize="marquee"
+                android:focusable="true"
+                android:gravity="center_vertical"
+                android:singleLine="true"
+                android:textAppearance="@style/TextAppearance.QS.Status"
+                android:visibility="gone" />
+
+            <com.android.systemui.qs.PageIndicator
+                android:id="@+id/footer_page_indicator"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_gravity="center_vertical"
+                android:visibility="gone" />
+
+            <View
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/qs_footer_actions_container"
+            android:layout_width="match_parent"
+            android:layout_height="48dp"
+            android:gravity="center_vertical">
+
             <com.android.systemui.statusbar.AlphaOptimizedImageView
                 android:id="@android:id/edit"
-                android:layout_width="@dimen/qs_footer_action_button_size"
+                android:layout_width="0dp"
                 android:layout_height="@dimen/qs_footer_action_button_size"
-                android:background="?android:attr/selectableItemBackgroundBorderless"
+                android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
+                android:layout_weight="1"
+                android:background="@drawable/qs_footer_action_chip_background"
                 android:clickable="true"
                 android:clipToPadding="false"
                 android:contentDescription="@string/accessibility_quick_settings_edit"
                 android:focusable="true"
                 android:padding="@dimen/qs_footer_icon_padding"
                 android:src="@*android:drawable/ic_mode_edit"
-                android:tint="?android:attr/colorForeground"/>
-
-            <TextView
-                android:id="@+id/build"
-                android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:clickable="true"
-                android:gravity="center_vertical"
-                android:focusable="true"
-                android:singleLine="true"
-                android:ellipsize="marquee"
-                android:textAppearance="@style/TextAppearance.QS.Status"
-                android:layout_marginEnd="4dp"
-                android:visibility="gone"/>
-         </com.android.keyguard.AlphaOptimizedLinearLayout>
-
-        <com.android.systemui.qs.PageIndicator
-            android:id="@+id/footer_page_indicator"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_gravity="center_vertical"
-            android:visibility="gone" />
-
-        <com.android.keyguard.AlphaOptimizedLinearLayout
-            android:id="@+id/qs_footer_actions_container"
-            android:layout_width="@integer/qs_footer_actions_width"
-            android:layout_height="match_parent"
-            android:layout_weight="@integer/qs_footer_actions_weight"
-            android:gravity="center_vertical|end" >
-
-            <com.android.systemui.statusbar.AlphaOptimizedImageView
-                android:id="@+id/pm_lite"
-                android:layout_width="@dimen/qs_footer_action_button_size"
-                android:layout_height="@dimen/qs_footer_action_button_size"
-                android:background="?android:attr/selectableItemBackgroundBorderless"
-                android:clickable="true"
-                android:clipToPadding="false"
-                android:contentDescription="@string/accessibility_quick_settings_power_menu"
-                android:focusable="true"
-                android:padding="@dimen/qs_footer_icon_padding"
-                android:src="@*android:drawable/ic_lock_power_off"
-                android:tint="?android:attr/colorForeground"
-                android:visibility="gone"
-                />
+                android:tint="?android:attr/colorForeground" />
 
             <com.android.systemui.statusbar.phone.MultiUserSwitch
                 android:id="@+id/multi_user_switch"
-                android:layout_width="@dimen/qs_footer_action_button_size"
+                android:layout_width="0dp"
                 android:layout_height="@dimen/qs_footer_action_button_size"
-                android:layout_alignParentEnd="true"
-                android:background="@drawable/ripple_drawable"
+                android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
+                android:layout_weight="1"
+                android:background="@drawable/qs_footer_action_chip_background"
                 android:focusable="true">
 
                 <ImageView
@@ -110,40 +102,58 @@
                     android:layout_width="@dimen/multi_user_avatar_expanded_size"
                     android:layout_height="@dimen/multi_user_avatar_expanded_size"
                     android:layout_gravity="center"
-                    android:scaleType="centerInside"/>
+                    android:scaleType="centerInside" />
             </com.android.systemui.statusbar.phone.MultiUserSwitch>
 
             <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
                 android:id="@+id/settings_button_container"
-                android:layout_width="@dimen/qs_footer_action_button_size"
+                android:layout_width="0dp"
                 android:layout_height="@dimen/qs_footer_action_button_size"
+                android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
+                android:background="@drawable/qs_footer_action_chip_background"
+                android:layout_weight="1"
                 android:clipChildren="false"
                 android:clipToPadding="false">
 
                 <com.android.systemui.statusbar.phone.SettingsButton
                     android:id="@+id/settings_button"
-                    style="@android:style/Widget.Material.Button.Borderless"
                     android:layout_width="match_parent"
-                    android:layout_height="match_parent"
+                    android:layout_height="@dimen/qs_footer_action_button_size"
                     android:layout_gravity="center"
-                    android:padding="@dimen/qs_footer_icon_padding"
-                    android:background="@drawable/ripple_drawable"
                     android:contentDescription="@string/accessibility_quick_settings_settings"
-                    android:src="@drawable/ic_settings"
+                    android:background="@drawable/qs_footer_action_chip_background_borderless"
+                    android:padding="@dimen/qs_footer_icon_padding"
                     android:scaleType="centerInside"
-                    android:tint="?android:attr/colorForeground"/>
+                    android:src="@drawable/ic_settings"
+                    android:tint="?android:attr/colorForeground" />
 
                 <com.android.systemui.statusbar.AlphaOptimizedImageView
                     android:id="@+id/tuner_icon"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
-                    android:paddingStart="36dp"
-                    android:paddingEnd="4dp"
+                    android:layout_width="8dp"
+                    android:layout_height="8dp"
+                    android:layout_gravity="center_horizontal|bottom"
+                    android:layout_marginBottom="@dimen/qs_footer_icon_padding"
                     android:src="@drawable/tuner"
                     android:tint="?android:attr/textColorTertiary"
-                    android:visibility="invisible"/>
+                    android:visibility="invisible" />
 
             </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
-        </com.android.keyguard.AlphaOptimizedLinearLayout>
+
+            <com.android.systemui.statusbar.AlphaOptimizedImageView
+                android:id="@+id/pm_lite"
+                android:layout_width="0dp"
+                android:layout_height="@dimen/qs_footer_action_button_size"
+                android:layout_weight="1"
+                android:background="@drawable/qs_footer_action_chip_background"
+                android:clickable="true"
+                android:clipToPadding="false"
+                android:focusable="true"
+                android:padding="@dimen/qs_footer_icon_padding"
+                android:src="@*android:drawable/ic_lock_power_off"
+                android:contentDescription="@string/accessibility_quick_settings_power_menu"
+                android:tint="?android:attr/colorForeground" />
+
+        </LinearLayout>
     </LinearLayout>
+
 </com.android.systemui.qs.QSFooterView>
diff --git a/packages/SystemUI/res/layout/qs_footer_impl_two_lines.xml b/packages/SystemUI/res/layout/qs_footer_impl_two_lines.xml
deleted file mode 100644
index 343b398..0000000
--- a/packages/SystemUI/res/layout/qs_footer_impl_two_lines.xml
+++ /dev/null
@@ -1,159 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-** Copyright 2021, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
--->
-
-<!-- Extends FrameLayout -->
-<com.android.systemui.qs.QSFooterView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/qs_footer"
-    android:layout_width="match_parent"
-    android:layout_height="@dimen/qs_footer_height"
-    android:layout_marginStart="@dimen/qs_footer_margin"
-    android:layout_marginEnd="@dimen/qs_footer_margin"
-    android:background="@android:color/transparent"
-    android:baselineAligned="false"
-    android:clickable="false"
-    android:clipChildren="false"
-    android:clipToPadding="false">
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical">
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="48dp"
-            android:layout_gravity="center_vertical">
-
-            <TextView
-                android:id="@+id/build"
-                android:layout_width="0dp"
-                android:layout_height="match_parent"
-                android:paddingStart="@dimen/qs_tile_margin_horizontal"
-                android:paddingEnd="4dp"
-                android:layout_weight="1"
-                android:clickable="true"
-                android:ellipsize="marquee"
-                android:focusable="true"
-                android:gravity="center_vertical"
-                android:singleLine="true"
-                android:textAppearance="@style/TextAppearance.QS.Status"
-                android:visibility="gone" />
-
-            <com.android.systemui.qs.PageIndicator
-                android:id="@+id/footer_page_indicator"
-                android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:layout_gravity="center_vertical"
-                android:visibility="gone" />
-
-            <View
-                android:layout_width="0dp"
-                android:layout_height="match_parent"
-                android:layout_weight="1" />
-
-        </LinearLayout>
-
-        <LinearLayout
-            android:id="@+id/qs_footer_actions_container"
-            android:layout_width="match_parent"
-            android:layout_height="48dp"
-            android:gravity="center_vertical">
-
-            <com.android.systemui.statusbar.AlphaOptimizedImageView
-                android:id="@android:id/edit"
-                android:layout_width="0dp"
-                android:layout_height="@dimen/qs_footer_action_button_size"
-                android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
-                android:layout_weight="1"
-                android:background="@drawable/qs_footer_action_chip_background"
-                android:clickable="true"
-                android:clipToPadding="false"
-                android:contentDescription="@string/accessibility_quick_settings_edit"
-                android:focusable="true"
-                android:padding="@dimen/qs_footer_icon_padding"
-                android:src="@*android:drawable/ic_mode_edit"
-                android:tint="?android:attr/colorForeground" />
-
-            <com.android.systemui.statusbar.phone.MultiUserSwitch
-                android:id="@+id/multi_user_switch"
-                android:layout_width="0dp"
-                android:layout_height="@dimen/qs_footer_action_button_size"
-                android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
-                android:layout_weight="1"
-                android:background="@drawable/qs_footer_action_chip_background"
-                android:focusable="true">
-
-                <ImageView
-                    android:id="@+id/multi_user_avatar"
-                    android:layout_width="@dimen/multi_user_avatar_expanded_size"
-                    android:layout_height="@dimen/multi_user_avatar_expanded_size"
-                    android:layout_gravity="center"
-                    android:scaleType="centerInside" />
-            </com.android.systemui.statusbar.phone.MultiUserSwitch>
-
-            <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
-                android:id="@+id/settings_button_container"
-                android:layout_width="0dp"
-                android:layout_height="@dimen/qs_footer_action_button_size"
-                android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
-                android:background="@drawable/qs_footer_action_chip_background"
-                android:layout_weight="1"
-                android:clipChildren="false"
-                android:clipToPadding="false">
-
-                <com.android.systemui.statusbar.phone.SettingsButton
-                    android:id="@+id/settings_button"
-                    android:layout_width="match_parent"
-                    android:layout_height="@dimen/qs_footer_action_button_size"
-                    android:layout_gravity="center"
-                    android:contentDescription="@string/accessibility_quick_settings_settings"
-                    android:background="@drawable/qs_footer_action_chip_background_borderless"
-                    android:padding="@dimen/qs_footer_icon_padding"
-                    android:scaleType="centerInside"
-                    android:src="@drawable/ic_settings"
-                    android:tint="?android:attr/colorForeground" />
-
-                <com.android.systemui.statusbar.AlphaOptimizedImageView
-                    android:id="@+id/tuner_icon"
-                    android:layout_width="8dp"
-                    android:layout_height="8dp"
-                    android:layout_gravity="center_horizontal|bottom"
-                    android:layout_marginBottom="@dimen/qs_footer_icon_padding"
-                    android:src="@drawable/tuner"
-                    android:tint="?android:attr/textColorTertiary"
-                    android:visibility="invisible" />
-
-            </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
-
-            <com.android.systemui.statusbar.AlphaOptimizedImageView
-                android:id="@+id/pm_lite"
-                android:layout_width="0dp"
-                android:layout_height="@dimen/qs_footer_action_button_size"
-                android:layout_weight="1"
-                android:background="@drawable/qs_footer_action_chip_background"
-                android:clickable="true"
-                android:clipToPadding="false"
-                android:focusable="true"
-                android:padding="@dimen/qs_footer_icon_padding"
-                android:src="@*android:drawable/ic_lock_power_off"
-                android:contentDescription="@string/accessibility_quick_settings_power_menu"
-                android:tint="?android:attr/colorForeground" />
-
-        </LinearLayout>
-    </LinearLayout>
-
-</com.android.systemui.qs.QSFooterView>
diff --git a/packages/SystemUI/res/layout/qs_paged_page.xml b/packages/SystemUI/res/layout/qs_paged_page.xml
index 5c8b2b0..c830773 100644
--- a/packages/SystemUI/res/layout/qs_paged_page.xml
+++ b/packages/SystemUI/res/layout/qs_paged_page.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-     Copyright (C) 2015 The Android Open Source Project
+     Copyright (C) 2020 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -14,8 +14,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<view
-    class="com.android.systemui.qs.PagedTileLayout$TilePage"
+<com.android.systemui.qs.SideLabelTileLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/tile_page"
     android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml b/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml
deleted file mode 100644
index c830773..0000000
--- a/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2020 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<com.android.systemui.qs.SideLabelTileLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/tile_page"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:clipChildren="false"
-    android:clipToPadding="false" />
diff --git a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml
index 46a7cf6..c3f1113 100644
--- a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml
+++ b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-     Copyright (C) 2015 The Android Open Source Project
+     Copyright (C) 2020 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -22,5 +22,4 @@
     android:layout_height="0dp"
     android:layout_weight="1"
     android:clipChildren="true"
-    android:paddingBottom="@dimen/qs_paged_tile_layout_padding_bottom">
-</com.android.systemui.qs.PagedTileLayout>
+    android:paddingBottom="@dimen/qs_paged_tile_layout_padding_bottom" />
diff --git a/packages/SystemUI/res/layout/qs_paged_tile_layout_side_labels.xml b/packages/SystemUI/res/layout/qs_paged_tile_layout_side_labels.xml
deleted file mode 100644
index efa2403..0000000
--- a/packages/SystemUI/res/layout/qs_paged_tile_layout_side_labels.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2020 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<com.android.systemui.qs.PagedTileLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:systemui="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/qs_pager"
-    android:layout_width="match_parent"
-    android:layout_height="0dp"
-    android:layout_weight="1"
-    android:clipChildren="true"
-    android:paddingBottom="@dimen/qs_paged_tile_layout_padding_bottom"
-    systemui:sideLabels="true" />
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index dc595ee..3d2a621 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -43,10 +43,7 @@
             android:background="@android:color/transparent"
             android:focusable="true"
             android:accessibilityTraversalBefore="@android:id/edit">
-            <ViewStub
-                android:id="@+id/qs_footer_stub"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"/>
+            <include layout="@layout/qs_footer_impl" />
             <include layout="@layout/qs_media_divider"
                 android:id="@+id/divider"/>
         </com.android.systemui.qs.QSPanel>
@@ -59,18 +56,4 @@
     <include android:id="@+id/qs_customize" layout="@layout/qs_customize_panel"
         android:visibility="gone" />
 
-    <FrameLayout
-        android:id="@+id/qs_drag_handle_view"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_horizontal"
-        android:elevation="4dp"
-        android:paddingBottom="5dp">
-        <View
-            android:layout_width="46dp"
-            android:layout_height="3dp"
-            android:background="@drawable/qs_footer_drag_handle" />
-    </FrameLayout>
-
-
 </com.android.systemui.qs.QSContainerImpl>
diff --git a/packages/SystemUI/res/layout/rounded_corners.xml b/packages/SystemUI/res/layout/rounded_corners.xml
index db892d7..04fe918 100644
--- a/packages/SystemUI/res/layout/rounded_corners.xml
+++ b/packages/SystemUI/res/layout/rounded_corners.xml
@@ -14,6 +14,8 @@
 ** See the License for the specific language governing permissions and
 ** limitations under the License.
 -->
+
+<!-- TODO: remove this in favor of requiring top and bottom layouts -->
 <com.android.systemui.RegionInterceptingFrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/rounded_corners_default"
@@ -26,6 +28,25 @@
         android:layout_gravity="left|top"
         android:tint="#ff000000"
         android:src="@drawable/rounded"/>
+
+    <FrameLayout
+        android:id="@+id/privacy_dot_left_container"
+        android:layout_height="@dimen/status_bar_height"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="@dimen/status_bar_padding_top"
+        android:layout_marginLeft="8dp"
+        android:layout_gravity="left|top"
+        android:visibility="invisible" >
+        <ImageView
+            android:id="@+id/privacy_dot_left"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center"
+            android:src="@drawable/system_animation_ongoing_dot"
+            android:visibility="visible" />
+    </FrameLayout>
+
+
     <ImageView
         android:id="@+id/right"
         android:layout_width="12dp"
@@ -33,4 +54,22 @@
         android:tint="#ff000000"
         android:layout_gravity="right|bottom"
         android:src="@drawable/rounded"/>
+    <FrameLayout
+        android:id="@+id/privacy_dot_right_container"
+        android:layout_height="@dimen/status_bar_height"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="@dimen/status_bar_padding_top"
+        android:layout_marginRight="8dp"
+        android:layout_gravity="right|top"
+        android:visibility="invisible" >
+        <ImageView
+            android:id="@+id/privacy_dot_right"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center"
+            android:src="@drawable/system_animation_ongoing_dot"
+            android:visibility="visible" />
+
+    </FrameLayout>
+
 </com.android.systemui.RegionInterceptingFrameLayout>
diff --git a/packages/SystemUI/res/layout/rounded_corners_bottom.xml b/packages/SystemUI/res/layout/rounded_corners_bottom.xml
index dde1248..720e47b 100644
--- a/packages/SystemUI/res/layout/rounded_corners_bottom.xml
+++ b/packages/SystemUI/res/layout/rounded_corners_bottom.xml
@@ -26,6 +26,24 @@
         android:layout_gravity="left|bottom"
         android:tint="#ff000000"
         android:src="@drawable/rounded_corner_bottom"/>
+
+    <FrameLayout
+        android:id="@+id/privacy_dot_left_container"
+        android:layout_height="@dimen/status_bar_height"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="@dimen/status_bar_padding_top"
+        android:layout_marginLeft="0dp"
+        android:layout_gravity="left|bottom"
+        android:visibility="invisible" >
+        <ImageView
+            android:id="@+id/privacy_dot"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_vertical|right"
+            android:src="@drawable/system_animation_ongoing_dot"
+            android:visibility="visible" />
+    </FrameLayout>
+
     <ImageView
         android:id="@+id/right"
         android:layout_width="12dp"
@@ -33,4 +51,21 @@
         android:tint="#ff000000"
         android:layout_gravity="right|bottom"
         android:src="@drawable/rounded_corner_bottom"/>
+    <FrameLayout
+        android:id="@+id/privacy_dot_right_container"
+        android:layout_height="@dimen/status_bar_height"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="@dimen/status_bar_padding_top"
+        android:layout_marginRight="0dp"
+        android:layout_gravity="right|bottom"
+        android:visibility="invisible" >
+        <ImageView
+            android:id="@+id/privacy_dot"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_vertical|left"
+            android:src="@drawable/system_animation_ongoing_dot"
+            android:visibility="visible" />
+    </FrameLayout>
+
 </com.android.systemui.RegionInterceptingFrameLayout>
diff --git a/packages/SystemUI/res/layout/rounded_corners_top.xml b/packages/SystemUI/res/layout/rounded_corners_top.xml
index 813c97d..6abe406 100644
--- a/packages/SystemUI/res/layout/rounded_corners_top.xml
+++ b/packages/SystemUI/res/layout/rounded_corners_top.xml
@@ -26,6 +26,24 @@
         android:layout_gravity="left|top"
         android:tint="#ff000000"
         android:src="@drawable/rounded_corner_top"/>
+
+    <FrameLayout
+        android:id="@+id/privacy_dot_left_container"
+        android:layout_height="@*android:dimen/status_bar_height_portrait"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="@dimen/status_bar_padding_top"
+        android:layout_marginLeft="0dp"
+        android:layout_gravity="left|top"
+        android:visibility="invisible" >
+        <ImageView
+            android:id="@+id/privacy_dot"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_vertical|right"
+            android:src="@drawable/system_animation_ongoing_dot"
+            android:visibility="visible" />
+    </FrameLayout>
+
     <ImageView
         android:id="@+id/right"
         android:layout_width="12dp"
@@ -33,4 +51,24 @@
         android:tint="#ff000000"
         android:layout_gravity="right|top"
         android:src="@drawable/rounded_corner_top"/>
+
+    <FrameLayout
+        android:id="@+id/privacy_dot_right_container"
+        android:layout_height="@*android:dimen/status_bar_height_portrait"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="@dimen/status_bar_padding_top"
+        android:layout_marginRight="0dp"
+        android:layout_gravity="right|top"
+        android:visibility="invisible" >
+        <ImageView
+            android:id="@+id/privacy_dot"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_vertical|left"
+            android:src="@drawable/system_animation_ongoing_dot"
+            android:visibility="visible" />
+
+    </FrameLayout>
+
+
 </com.android.systemui.RegionInterceptingFrameLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
index 5c77d16..91220e5 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_footer.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
@@ -46,6 +46,7 @@
             android:layout_gravity="end"
             android:background="@drawable/notif_footer_btn_background"
             android:focusable="true"
+            android:textColor="@color/notif_pill_text"
             android:contentDescription="@string/accessibility_clear_all"
             android:text="@string/clear_all_notifications_text"
         />
diff --git a/packages/SystemUI/res/layout/system_event_animation_window.xml b/packages/SystemUI/res/layout/system_event_animation_window.xml
new file mode 100644
index 0000000..c92dec9
--- /dev/null
+++ b/packages/SystemUI/res/layout/system_event_animation_window.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center_vertical|end"
+    android:paddingTop="@dimen/status_bar_padding_top"
+    android:paddingEnd="8dp"
+    >
+
+    <ImageView
+        android:id="@+id/dot_view"
+        android:layout_width="10dp"
+        android:layout_height="10dp"
+        android:layout_gravity="center_vertical|end"
+        android:src="@drawable/system_animation_ongoing_dot"
+        android:visibility="invisible"
+        />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-h740dp-port/dimens.xml b/packages/SystemUI/res/values-h740dp-port/dimens.xml
deleted file mode 100644
index 966066f..0000000
--- a/packages/SystemUI/res/values-h740dp-port/dimens.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<resources>
-    <dimen name="qs_tile_height">106dp</dimen>
-    <dimen name="qs_tile_margin_vertical">24dp</dimen>
-
-    <!-- The height of the qs customize header. Should be
-         (qs_panel_padding_top (48dp) +  brightness_mirror_height (48dp) + qs_tile_margin_top (18dp)) -
-         (Toolbar_minWidth (56dp) + qs_tile_margin_top_bottom (12dp))
-    -->
-    <dimen name="qs_customize_header_min_height">46dp</dimen>
-    <dimen name="qs_tile_margin_top">18dp</dimen>
-</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index 46ec23c..ea456d8 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -20,12 +20,11 @@
 <!-- These resources are around just to allow their values to be customized
      for different hardware and product builds. -->
 <resources>
-    <!-- The maximum number of tiles in the QuickQSPanel -->
-    <integer name="quick_qs_panel_max_columns">6</integer>
-
     <!-- The maximum number of rows in the QuickSettings -->
     <integer name="quick_settings_max_rows">2</integer>
 
+    <integer name="quick_settings_num_columns">4</integer>
+
     <!-- The number of columns that the top level tiles span in the QuickSettings -->
     <integer name="quick_settings_user_time_settings_tile_span">2</integer>
 
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 51d7b8e..007f81b 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -23,12 +23,16 @@
     <dimen name="docked_divider_handle_height">16dp</dimen>
 
     <dimen name="qs_tile_margin_top">8dp</dimen>
-    <dimen name="qs_tile_margin_vertical">0dp</dimen>
+
+    <!-- The height of the qs customize header. Should be
+    (qs_panel_padding_top (48dp) +  brightness_mirror_height (48dp) + qs_tile_margin_top (8dp)) -
+    (Toolbar_minWidth (56dp) + qs_tile_margin_top_bottom (4dp))
+    -->
+    <dimen name="qs_customize_header_min_height">44dp</dimen>
 
     <dimen name="battery_detail_graph_space_top">9dp</dimen>
     <dimen name="battery_detail_graph_space_bottom">9dp</dimen>
 
-    <integer name="quick_settings_num_columns">4</integer>
     <dimen name="qs_detail_margin_top">0dp</dimen>
 
     <dimen name="volume_tool_tip_right_margin">136dp</dimen>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index e6c5bd0..8f88950 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -33,7 +33,6 @@
     <!-- The color of the text inside a notification -->
     <color name="notification_primary_text_color">@*android:color/notification_primary_text_color_dark</color>
 
-    <color name="notif_pill_background">@*android:color/surface_dark</color>
     <color name="notif_pill_text">@android:color/system_neutral1_50</color>
 
     <color name="notification_guts_link_icon_tint">@color/GM2_grey_500</color>
diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index a6321fe..e2b2e25 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -15,8 +15,6 @@
   ~ limitations under the License
   -->
 <resources>
-    <integer name="quick_settings_num_columns">3</integer>
-
     <!-- Max number of columns for quick controls area -->
     <integer name="controls_max_columns">2</integer>
 
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
deleted file mode 100644
index 302e5e4..0000000
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (c) 2012, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
-*/
--->
-<resources>
-    <dimen name="keyguard_clock_notifications_margin">36dp</dimen>
-
-    <dimen name="keyguard_indication_margin_bottom">80dp</dimen>
-
-    <!-- Screen pinning request width (just a little bit bigger than the three buttons here -->
-    <dimen name="screen_pinning_request_width">490dp</dimen>
-    <!-- Screen pinning request bottom button circle widths -->
-    <dimen name="screen_pinning_request_button_width">162dp</dimen>
-    <!-- Screen pinning request, controls padding on bigger screens, bigger nav bar -->
-    <dimen name="screen_pinning_request_frame_padding">39dp</dimen>
-    <!-- Screen pinning request side views to match nav bar
-         In sw600dp we want the buttons centered so this fills the space,
-         (screen_pinning_request_width - 3 * screen_pinning_request_button_width) / 2 -->
-    <dimen name="screen_pinning_request_side_width">2dp</dimen>
-
-    <dimen name="navigation_key_width">162dp</dimen>
-    <dimen name="navigation_key_padding">42dp</dimen>
-
-    <dimen name="battery_detail_graph_space_top">27dp</dimen>
-    <dimen name="battery_detail_graph_space_bottom">27dp</dimen>
-
-    <dimen name="qs_tile_margin_top">32dp</dimen>
-    <dimen name="qs_brightness_padding_top">6dp</dimen>
-    <dimen name="qs_detail_margin_top">28dp</dimen>
-
-    <!-- In split shade mode notifications should be aligned to QS header so the value should be
-     adjusted to qs header height and height of centered content inside of it:
-    (quick_qs_offset_height (48dp) - ongoing_appops_chip_height (24dp) ) / 2 -->
-    <dimen name="notifications_top_padding_split_shade">12dp</dimen>
-</resources>
diff --git a/packages/SystemUI/res/values-w550dp-land/config.xml b/packages/SystemUI/res/values-w550dp-land/config.xml
deleted file mode 100644
index a33f131..0000000
--- a/packages/SystemUI/res/values-w550dp-land/config.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2016, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
-     for different hardware and product builds. -->
-<resources>
-    <integer name="quick_settings_num_columns">6</integer>
-</resources>
diff --git a/packages/SystemUI/res/values-w650dp-land/dimens.xml b/packages/SystemUI/res/values-w650dp-land/dimens.xml
index 108d6cf..97b6da1 100644
--- a/packages/SystemUI/res/values-w650dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-w650dp-land/dimens.xml
@@ -16,6 +16,5 @@
   -->
 <resources>
     <!-- Standard notification width + gravity -->
-    <dimen name="notification_panel_width">644dp</dimen>
-
+    <dimen name="notification_panel_width">-1px</dimen> <!-- match_parent -->
 </resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index e4bdbf3..f489fe8 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -176,10 +176,6 @@
         <attr name="android:alpha" />
     </declare-styleable>
 
-    <declare-styleable name="PagedTileLayout">
-        <attr name="sideLabels" format="boolean"/>
-    </declare-styleable>
-
     <declare-styleable name="CropView">
         <attr name="handleThickness" />
         <attr name="handleColor" />
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index f699198..bf13c21 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -157,7 +157,6 @@
     <color name="minimize_dock_shadow_end">#00000000</color>
 
     <color name="default_remote_input_background">@*android:color/notification_default_color</color>
-    <color name="notif_pill_background">@*android:color/surface_light</color>
     <color name="notif_pill_text">@android:color/system_neutral1_900</color>
     <color name="remote_input_accent">?android:attr/colorAccent</color>
 
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index ac41a25..5feb957 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -86,13 +86,13 @@
     <bool name="config_navigation_bar_enable_auto_dim_no_visible_wallpaper">true</bool>
 
     <!-- The maximum number of tiles in the QuickQSPanel -->
-    <integer name="quick_qs_panel_max_columns">6</integer>
+    <integer name="quick_qs_panel_max_columns">4</integer>
 
     <!-- The number of columns in the QuickSettings -->
-    <integer name="quick_settings_num_columns">3</integer>
+    <integer name="quick_settings_num_columns">2</integer>
 
     <!-- The number of rows in the QuickSettings -->
-    <integer name="quick_settings_max_rows">3</integer>
+    <integer name="quick_settings_max_rows">4</integer>
 
     <!-- The number of columns that the top level tiles span in the QuickSettings -->
     <integer name="quick_settings_user_time_settings_tile_span">1</integer>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 5767174..62ac75e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -407,10 +407,10 @@
 
     <!-- The height of the quick settings footer that holds the user switcher, settings icon,
          etc. -->
-    <dimen name="qs_footer_height">48dp</dimen>
+    <dimen name="qs_footer_height">96dp</dimen>
 
     <!-- The size of each of the icon buttons in the QS footer -->
-    <dimen name="qs_footer_action_button_size">@dimen/qs_footer_height</dimen>
+    <dimen name="qs_footer_action_button_size">48dp</dimen>
 
     <dimen name="qs_footer_action_corner_radius">20dp</dimen>
 
@@ -529,25 +529,25 @@
     <dimen name="pull_span_min">25dp</dimen>
 
     <dimen name="qs_corner_radius">14dp</dimen>
-    <dimen name="qs_tile_height">96dp</dimen>
+    <dimen name="qs_tile_height">88dp</dimen>
     <!--notification_side_paddings + notification_content_margin_start - (qs_quick_tile_size - qs_tile_background_size) / 2 -->
     <dimen name="qs_tile_layout_margin_side">18dp</dimen>
-    <dimen name="qs_tile_margin_horizontal">18dp</dimen>
+    <dimen name="qs_tile_margin_horizontal">8dp</dimen>
     <dimen name="qs_tile_margin_horizontal_two_line">2dp</dimen>
-    <dimen name="qs_tile_margin_vertical">2dp</dimen>
-    <dimen name="qs_tile_margin_top_bottom">12dp</dimen>
-    <dimen name="qs_tile_margin_top_bottom_negative">-12dp</dimen>
+    <dimen name="qs_tile_margin_vertical">@dimen/qs_tile_margin_horizontal</dimen>
+    <dimen name="qs_tile_margin_top_bottom">4dp</dimen>
+    <dimen name="qs_tile_margin_top_bottom_negative">-4dp</dimen>
     <!-- The height of the qs customize header. Should be
-         (qs_panel_padding_top (48dp) +  brightness_mirror_height (48dp) + qs_tile_margin_top (0dp)) -
-         (Toolbar_minWidth (56dp) + qs_tile_margin_top_bottom (12dp))
+         (qs_panel_padding_top (48dp) +  brightness_mirror_height (48dp) + qs_tile_margin_top (18dp)) -
+         (Toolbar_minWidth (56dp) + qs_tile_margin_top_bottom (4dp))
     -->
-    <dimen name="qs_customize_header_min_height">28dp</dimen>
-    <dimen name="qs_tile_margin_top">0dp</dimen>
+    <dimen name="qs_customize_header_min_height">54dp</dimen>
+    <dimen name="qs_tile_margin_top">18dp</dimen>
     <dimen name="qs_tile_icon_background_stroke_width">-1dp</dimen>
-    <dimen name="qs_tile_background_size">44dp</dimen>
+    <dimen name="qs_tile_background_size">56dp</dimen>
     <dimen name="qs_icon_size">20dp</dimen>
     <dimen name="qs_label_container_margin">10dp</dimen>
-    <dimen name="qs_quick_tile_size">48dp</dimen>
+    <dimen name="qs_quick_tile_size">60dp</dimen>
     <dimen name="qs_quick_tile_padding">12dp</dimen>
     <dimen name="qs_header_gear_translation">16dp</dimen>
     <dimen name="qs_header_tile_margin_bottom">18dp</dimen>
@@ -557,9 +557,9 @@
          Scaled @dimen/qs_page_indicator-width by .4f.
     -->
     <dimen name="qs_page_indicator_dot_width">6.4dp</dimen>
-    <dimen name="qs_tile_side_label_padding">6dp</dimen>
+    <dimen name="qs_tile_side_label_padding">12dp</dimen>
     <dimen name="qs_tile_icon_size">24dp</dimen>
-    <dimen name="qs_tile_text_size">12sp</dimen>
+    <dimen name="qs_tile_text_size">14sp</dimen>
     <dimen name="qs_tile_divider_height">1dp</dimen>
     <dimen name="qs_panel_padding">16dp</dimen>
     <dimen name="qs_dual_tile_height">112dp</dimen>
@@ -570,7 +570,7 @@
     <dimen name="qs_tile_padding_bottom">16dp</dimen>
     <dimen name="qs_tile_spacing">4dp</dimen>
     <dimen name="qs_panel_padding_bottom">0dp</dimen>
-    <dimen name="qs_panel_padding_top">@dimen/qs_header_tooltip_height</dimen>
+    <dimen name="qs_panel_padding_top">48dp</dimen>
     <dimen name="qs_detail_header_height">56dp</dimen>
     <dimen name="qs_detail_header_padding">0dp</dimen>
     <dimen name="qs_detail_image_width">56dp</dimen>
@@ -594,7 +594,6 @@
     <dimen name="qs_detail_item_icon_width">32dp</dimen>
     <dimen name="qs_detail_item_icon_marginStart">0dp</dimen>
     <dimen name="qs_detail_item_icon_marginEnd">20dp</dimen>
-    <dimen name="qs_header_tooltip_height">48dp</dimen>
     <dimen name="qs_header_alarm_icon_size">@dimen/status_bar_icon_drawing_size</dimen>
     <dimen name="qs_header_mobile_icon_size">@dimen/status_bar_icon_drawing_size</dimen>
     <dimen name="qs_header_alarm_text_margin_start">6dp</dimen>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index bbf2048..5827f4e 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -28,8 +28,6 @@
     <!-- b/171917882 -->
     <bool name="flag_notification_twocolumn">false</bool>
 
-    <bool name="flag_qs_labels">false</bool>
-
     <!-- AOD/Lockscreen alternate layout -->
     <bool name="flag_keyguard_layout">false</bool>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4ae1c936..e55142b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2012,7 +2012,7 @@
     <string name="notification_menu_settings_action">Settings</string>
 
     <!-- Notification: Snooze panel: Snooze undo button label. [CHAR LIMIT=50]-->
-    <string name="snooze_undo">UNDO</string>
+    <string name="snooze_undo">Undo</string>
 
     <!-- Notification: Snooze panel: message indicating how long the notification was snoozed for. [CHAR LIMIT=100]-->
     <string name="snoozed_for_time">Snoozed for <xliff:g id="time_amount" example="15 minutes">%1$s</xliff:g></string>
@@ -2953,10 +2953,7 @@
     [CHAR LIMIT=NONE] -->
     <string name="battery_state_unknown_notification_text">Tap for more information</string>
 
-    <!-- No translation [CHAR LIMIT=0] -->
-    <string name="qs_remove_labels" translatable="false"></string>
-
-    <string name="qs_tile_label_fontFamily" translatable="false">@*android:string/config_headlineFontFamily</string>
+    <string name="qs_tile_label_fontFamily" translatable="false">@*android:string/config_headlineFontFamilyMedium</string>
 
     <!-- Secondary label for alarm tile when there is no next alarm information [CHAR LIMIT=20] -->
     <string name="qs_alarm_tile_no_alarm">No alarm set</string>
diff --git a/packages/SystemUI/res/xml/people_space_widget_info.xml b/packages/SystemUI/res/xml/people_space_widget_info.xml
index e386147..68e6ca8 100644
--- a/packages/SystemUI/res/xml/people_space_widget_info.xml
+++ b/packages/SystemUI/res/xml/people_space_widget_info.xml
@@ -26,5 +26,5 @@
     android:previewLayout="@layout/people_space_placeholder_layout"
     android:resizeMode="horizontal|vertical"
     android:configure="com.android.systemui.people.PeopleSpaceActivity"
-    android:initialLayout="@layout/people_space_placeholder_layout">
+    android:initialLayout="@layout/people_space_initial_layout">
 </appwidget-provider>
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index a686fc0..cbfdce5 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -79,6 +79,8 @@
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
 import com.android.systemui.statusbar.notification.NotificationFilter;
@@ -352,6 +354,8 @@
     @Inject Lazy<DeviceConfigProxy> mDeviceConfigProxy;
     @Inject Lazy<NavigationBarOverlayController> mNavbarButtonsControllerLazy;
     @Inject Lazy<TelephonyListenerManager> mTelephonyListenerManager;
+    @Inject Lazy<SystemStatusAnimationScheduler> mSystemStatusAnimationSchedulerLazy;
+    @Inject Lazy<PrivacyDotViewController> mPrivacyDotViewControllerLazy;
 
     @Inject
     public Dependency() {
@@ -561,6 +565,10 @@
 
         mProviders.put(NavigationBarOverlayController.class, mNavbarButtonsControllerLazy::get);
 
+        mProviders.put(SystemStatusAnimationScheduler.class,
+                mSystemStatusAnimationSchedulerLazy::get);
+        mProviders.put(PrivacyDotViewController.class, mPrivacyDotViewControllerLazy::get);
+
         Dependency.setInstance(this);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 5fa98bc..d07723e 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -88,6 +88,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.qs.SecureSetting;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.events.PrivacyDotViewController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 import com.android.systemui.util.settings.SecureSettings;
@@ -126,6 +127,7 @@
     private DisplayManager.DisplayListener mDisplayListener;
     private CameraAvailabilityListener mCameraListener;
     private final UserTracker mUserTracker;
+    private final PrivacyDotViewController mDotViewController;
 
     //TODO: These are piecemeal being updated to Points for now to support non-square rounded
     // corners. for now it is only supposed when reading the intrinsic size from the drawables with
@@ -140,6 +142,11 @@
     protected View[] mOverlays;
     @Nullable
     private DisplayCutoutView[] mCutoutViews;
+    //TODO:
+    View mTopLeftDot;
+    View mTopRightDot;
+    View mBottomLeftDot;
+    View mBottomRightDot;
     private float mDensity;
     private WindowManager mWindowManager;
     private int mRotation;
@@ -147,6 +154,8 @@
     private Handler mHandler;
     private boolean mPendingRotationChange;
     private boolean mIsRoundedCornerMultipleRadius;
+    private int mStatusBarHeightPortrait;
+    private int mStatusBarHeightLandscape;
 
     private CameraAvailabilityListener.CameraTransitionCallback mCameraTransitionCallback =
             new CameraAvailabilityListener.CameraTransitionCallback() {
@@ -205,13 +214,15 @@
             SecureSettings secureSettings,
             BroadcastDispatcher broadcastDispatcher,
             TunerService tunerService,
-            UserTracker userTracker) {
+            UserTracker userTracker,
+            PrivacyDotViewController dotViewController) {
         super(context);
         mMainHandler = handler;
         mSecureSettings = secureSettings;
         mBroadcastDispatcher = broadcastDispatcher;
         mTunerService = tunerService;
         mUserTracker = userTracker;
+        mDotViewController = dotViewController;
     }
 
     @Override
@@ -222,6 +233,7 @@
         }
         mHandler = startHandlerThread();
         mHandler.post(this::startOnScreenDecorationsThread);
+        mDotViewController.setUiExecutor(mHandler::post);
     }
 
     @VisibleForTesting
@@ -286,6 +298,7 @@
 
     private void setupDecorations() {
         if (hasRoundedCorners() || shouldDrawCutout()) {
+            updateStatusBarHeight();
             final DisplayCutout cutout = getCutout();
             final Rect[] bounds = cutout == null ? null : cutout.getBoundingRectsAll();
             int rotatedPos;
@@ -298,6 +311,10 @@
                     removeOverlay(i);
                 }
             }
+            // Overlays have been created, send the dots to the controller
+            //TODO: need a better way to do this
+            mDotViewController.initialize(
+                    mTopLeftDot, mTopRightDot, mBottomLeftDot, mBottomRightDot);
         } else {
             removeAllOverlays();
         }
@@ -431,14 +448,21 @@
     private View overlayForPosition(@BoundsPosition int pos) {
         switch (pos) {
             case BOUNDS_POSITION_TOP:
-                return LayoutInflater.from(mContext)
+            case BOUNDS_POSITION_LEFT:
+                View top = LayoutInflater.from(mContext)
                         .inflate(R.layout.rounded_corners_top, null);
+                mTopLeftDot = top.findViewById(R.id.privacy_dot_left_container);
+                mTopRightDot = top.findViewById(R.id.privacy_dot_right_container);
+                return top;
             case BOUNDS_POSITION_BOTTOM:
-                return LayoutInflater.from(mContext)
+            case BOUNDS_POSITION_RIGHT:
+                View bottom =  LayoutInflater.from(mContext)
                         .inflate(R.layout.rounded_corners_bottom, null);
+                mBottomLeftDot = bottom.findViewById(R.id.privacy_dot_left_container);
+                mBottomRightDot = bottom.findViewById(R.id.privacy_dot_right_container);
+                return bottom;
             default:
-                return LayoutInflater.from(mContext)
-                        .inflate(R.layout.rounded_corners, null);
+                throw new IllegalArgumentException("Unknown bounds position");
         }
     }
 
@@ -575,6 +599,11 @@
             View child;
             for (int j = 0; j < size; j++) {
                 child = ((ViewGroup) mOverlays[i]).getChildAt(j);
+                if (child.getId() == R.id.privacy_dot_left_container
+                        || child.getId() == R.id.privacy_dot_right_container) {
+                    // Exclude privacy dot from color inversion (for now?)
+                    continue;
+                }
                 if (child instanceof ImageView) {
                     ((ImageView) child).setImageTintList(tintList);
                 } else if (child instanceof DisplayCutoutView) {
@@ -611,10 +640,15 @@
         Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(),
                 "must call on " + mHandler.getLooper().getThread()
                         + ", but was " + Thread.currentThread());
+
+        int newRotation = mContext.getDisplay().getRotation();
+        if (mRotation != newRotation) {
+            mDotViewController.updateRotation(newRotation);
+        }
+
         if (mPendingRotationChange) {
             return;
         }
-        int newRotation = mContext.getDisplay().getRotation();
         if (newRotation != mRotation) {
             mRotation = newRotation;
 
@@ -630,6 +664,14 @@
         }
     }
 
+    private void updateStatusBarHeight() {
+        mStatusBarHeightLandscape = mContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.status_bar_height_landscape);
+        mStatusBarHeightPortrait = mContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.status_bar_height_portrait);
+        mDotViewController.setStatusBarHeights(mStatusBarHeightPortrait, mStatusBarHeightLandscape);
+    }
+
     private void updateRoundedCornerRadii() {
         // We should eventually move to just using the intrinsic size of the drawables since
         // they should be sized to the exact pixels they want to cover. Therefore I'm purposely not
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java
index 2661d89..b544599 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java
@@ -60,17 +60,31 @@
     /**
      * Returns a copy of the list containing all the active AppOps that the controller tracks.
      *
-     * @return List of active AppOps information
+     * @return List of active AppOps information, without paused elements.
      */
     List<AppOpItem> getActiveAppOps();
 
     /**
+     * Returns a copy of the list containing all the active AppOps that the controller tracks.
+     *
+     * @param showPaused {@code true} to also obtain paused items. {@code false} otherwise.
+     * @return List of active AppOps information
+     */
+    List<AppOpItem> getActiveAppOps(boolean showPaused);
+
+    /**
      * Returns a copy of the list containing all the active AppOps that the controller tracks, for
      * a given user id.
      *
      * @param userId User id to track
+     * @param showPaused {@code true} to also obtain paused items. {@code false} otherwise.
      *
      * @return List of active AppOps information for that user id
      */
-    List<AppOpItem> getActiveAppOpsForUser(int userId);
+    List<AppOpItem> getActiveAppOpsForUser(int userId, boolean showPaused);
+
+    /**
+     * @return whether this controller is considering the microphone as muted.
+     */
+    boolean isMicMuted();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index 994401d..534f93e 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -241,9 +241,9 @@
             AppOpItem item = getAppOpItemLocked(mActiveItems, code, uid, packageName);
             if (item == null && active) {
                 item = new AppOpItem(code, uid, packageName, mClock.elapsedRealtime());
-                if (code == AppOpsManager.OP_RECORD_AUDIO) {
+                if (isOpMicrophone(code)) {
                     item.setDisabled(isAnyRecordingPausedLocked(uid));
-                } else if (code == AppOpsManager.OP_CAMERA) {
+                } else if (isOpCamera(code)) {
                     item.setDisabled(mCameraDisabled);
                 }
                 mActiveItems.add(item);
@@ -298,6 +298,11 @@
         return PermissionManager.shouldShowPackageForIndicatorCached(mContext, packageName);
     }
 
+    @WorkerThread
+    public List<AppOpItem> getActiveAppOps() {
+        return getActiveAppOps(false);
+    }
+
     /**
      * Returns a copy of the list containing all the active AppOps that the controller tracks.
      *
@@ -306,8 +311,8 @@
      * @return List of active AppOps information
      */
     @WorkerThread
-    public List<AppOpItem> getActiveAppOps() {
-        return getActiveAppOpsForUser(UserHandle.USER_ALL);
+    public List<AppOpItem> getActiveAppOps(boolean showPaused) {
+        return getActiveAppOpsForUser(UserHandle.USER_ALL, showPaused);
     }
 
     /**
@@ -321,7 +326,7 @@
      * @return List of active AppOps information for that user id
      */
     @WorkerThread
-    public List<AppOpItem> getActiveAppOpsForUser(int userId) {
+    public List<AppOpItem> getActiveAppOpsForUser(int userId, boolean showPaused) {
         Assert.isNotMainThread();
         List<AppOpItem> list = new ArrayList<>();
         synchronized (mActiveItems) {
@@ -330,7 +335,8 @@
                 AppOpItem item = mActiveItems.get(i);
                 if ((userId == UserHandle.USER_ALL
                         || UserHandle.getUserId(item.getUid()) == userId)
-                        && isUserVisible(item.getPackageName()) && !item.isDisabled()) {
+                        && isUserVisible(item.getPackageName())
+                        && (showPaused || !item.isDisabled())) {
                     list.add(item);
                 }
             }
@@ -441,9 +447,9 @@
                 AppOpItem item = mActiveItems.get(i);
 
                 boolean paused = false;
-                if (item.getCode() == AppOpsManager.OP_RECORD_AUDIO) {
+                if (isOpMicrophone(item.getCode())) {
                     paused = isAnyRecordingPausedLocked(item.getUid());
-                } else if (item.getCode() == AppOpsManager.OP_CAMERA) {
+                } else if (isOpCamera(item.getCode())) {
                     paused = mCameraDisabled;
                 }
 
@@ -502,6 +508,19 @@
         });
     }
 
+    @Override
+    public boolean isMicMuted() {
+        return mMicMuted;
+    }
+
+    private boolean isOpCamera(int op) {
+        return op == AppOpsManager.OP_CAMERA || op == AppOpsManager.OP_PHONE_CALL_CAMERA;
+    }
+
+    private boolean isOpMicrophone(int op) {
+        return op == AppOpsManager.OP_RECORD_AUDIO || op == AppOpsManager.OP_PHONE_CALL_MICROPHONE;
+    }
+
     protected class H extends Handler {
         H(Looper looper) {
             super(looper);
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
index f87ea7c..feb27d80 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
@@ -29,6 +29,7 @@
 import androidx.annotation.MainThread
 import androidx.annotation.VisibleForTesting
 import androidx.annotation.WorkerThread
+import com.android.systemui.appops.AppOpsController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -48,7 +49,6 @@
         return PrivacyDialog(context, list, starter)
     }
 }
-
 /**
  * Controller for [PrivacyDialog].
  *
@@ -66,6 +66,7 @@
     private val uiExecutor: Executor,
     private val privacyLogger: PrivacyLogger,
     private val keyguardStateController: KeyguardStateController,
+    private val appOpsController: AppOpsController,
     @VisibleForTesting private val dialogProvider: DialogProvider
 ) {
 
@@ -79,7 +80,8 @@
         @Background backgroundExecutor: Executor,
         @Main uiExecutor: Executor,
         privacyLogger: PrivacyLogger,
-        keyguardStateController: KeyguardStateController
+        keyguardStateController: KeyguardStateController,
+        appOpsController: AppOpsController
     ) : this(
             permissionManager,
             packageManager,
@@ -90,6 +92,7 @@
             uiExecutor,
             privacyLogger,
             keyguardStateController,
+            appOpsController,
             defaultDialogProvider
     )
 
@@ -127,7 +130,9 @@
     }
 
     @WorkerThread
-    private fun permGroupUsage(): List<PermGroupUsage> = permissionManager.indicatorAppOpUsageData
+    private fun permGroupUsage(): List<PermGroupUsage> {
+        return permissionManager.getIndicatorAppOpUsageData(appOpsController.isMicMuted)
+    }
 
     /**
      * Show the [PrivacyDialog]
@@ -261,4 +266,4 @@
             starter: (String, Int) -> Unit
         ): PrivacyDialog
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
index 4c617ed..63ec6e5 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
@@ -54,7 +54,8 @@
 data class PrivacyItem(
     val privacyType: PrivacyType,
     val application: PrivacyApplication,
-    val timeStampElapsed: Long = UNKNOWN_TIMESTAMP
+    val timeStampElapsed: Long = UNKNOWN_TIMESTAMP,
+    val paused: Boolean = false
 ) {
     val log = "(${privacyType.logName}, ${application.packageName}(${application.uid}), " +
             "$timeStampElapsed)"
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index f7e2a31..8b27b6e 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -257,7 +257,7 @@
             privacyList = emptyList()
             return
         }
-        val list = appOpsController.getActiveAppOpsForUser(UserHandle.USER_ALL).filter {
+        val list = appOpsController.getActiveAppOps(true).filter {
             UserHandle.getUserId(it.uid) in currentUserIds ||
                     it.code == AppOpsManager.OP_PHONE_CALL_MICROPHONE ||
                     it.code == AppOpsManager.OP_PHONE_CALL_CAMERA
@@ -279,7 +279,9 @@
 
         // Anything earlier than this timestamp can be removed
         val removeBeforeTime = systemClock.elapsedRealtime() - TIME_TO_HOLD_INDICATORS
-        val mustKeep = privacyList.filter { it.timeStampElapsed > removeBeforeTime && it !in list }
+        val mustKeep = privacyList.filter {
+            it.timeStampElapsed > removeBeforeTime && !(it isIn list)
+        }
 
         // There are items we must keep because they haven't been around for enough time.
         if (mustKeep.isNotEmpty()) {
@@ -291,7 +293,18 @@
             logger.logPrivacyItemsUpdateScheduled(delay)
             holdingRunnableCanceler = bgExecutor.executeDelayed(updateListAndNotifyChanges, delay)
         }
-        return list + mustKeep
+        return list.filter { !it.paused } + mustKeep
+    }
+
+    /**
+     * Ignores the paused status to determine if the element is in the list
+     */
+    private infix fun PrivacyItem.isIn(list: List<PrivacyItem>): Boolean {
+        return list.any {
+            it.privacyType == privacyType &&
+                    it.application == application &&
+                    it.timeStampElapsed == timeStampElapsed
+        }
     }
 
     private fun toPrivacyItem(appOpItem: AppOpItem): PrivacyItem? {
@@ -308,7 +321,7 @@
             return null
         }
         val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid)
-        return PrivacyItem(type, app, appOpItem.timeStartedElapsed)
+        return PrivacyItem(type, app, appOpItem.timeStartedElapsed, appOpItem.isDisabled)
     }
 
     interface Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 32723b4..f7fa5bf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -8,7 +8,6 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.content.res.TypedArray;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.util.AttributeSet;
@@ -71,8 +70,6 @@
     private int mMinRows = 1;
     private int mMaxColumns = TileLayout.NO_MAX_COLUMNS;
 
-    private final boolean mSideLabels;
-
     public PagedTileLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
         mScroller = new Scroller(context, SCROLL_CUBIC);
@@ -83,14 +80,9 @@
         mLayoutDirection = getLayoutDirection();
         mClippingRect = new Rect();
 
-        TypedArray t = context.getTheme().obtainStyledAttributes(
-                attrs, R.styleable.PagedTileLayout, 0, 0);
-        mSideLabels = t.getBoolean(R.styleable.PagedTileLayout_sideLabels, false);
-        t.recycle();
-        if (mSideLabels) {
-            setPageMargin(context.getResources().getDimensionPixelOffset(
+        // Make sure there's a space between pages when scroling
+        setPageMargin(context.getResources().getDimensionPixelOffset(
                     R.dimen.qs_tile_margin_horizontal));
-        }
     }
     private int mLastMaxHeight = -1;
 
@@ -228,8 +220,7 @@
 
     private TileLayout createTileLayout() {
         TileLayout page = (TileLayout) LayoutInflater.from(getContext())
-                .inflate(mSideLabels ? R.layout.qs_paged_page_side_labels
-                        : R.layout.qs_paged_page, this, false);
+                .inflate(R.layout.qs_paged_page, this, false);
         page.setMinRows(mMinRows);
         page.setMaxColumns(mMaxColumns);
         return page;
@@ -345,9 +336,8 @@
         // Update bottom padding, useful for removing extra space once the panel page indicator is
         // hidden.
         Resources res = getContext().getResources();
-        if (mSideLabels) {
-            setPageMargin(res.getDimensionPixelOffset(R.dimen.qs_tile_margin_horizontal));
-        }
+        setPageMargin(res.getDimensionPixelOffset(R.dimen.qs_tile_margin_horizontal));
+
         setPadding(0, 0, 0,
                 getContext().getResources().getDimensionPixelSize(
                         R.dimen.qs_paged_tile_layout_padding_bottom));
@@ -550,18 +540,6 @@
                 }
             };
 
-    public static class TilePage extends TileLayout {
-
-        public TilePage(Context context, AttributeSet attrs) {
-            super(context, attrs);
-        }
-
-        public boolean isFull() {
-            return mRecords.size() >= maxTiles();
-        }
-
-    }
-
     private final PagerAdapter mAdapter = new PagerAdapter() {
         @Override
         public void destroyItem(ViewGroup container, int position, Object object) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index ea471b9..cefcd4a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -33,7 +33,6 @@
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.qs.tileimpl.HeightOverrideable;
 import com.android.systemui.statusbar.CrossFadeHelper;
-import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 
@@ -102,14 +101,13 @@
     private final Executor mExecutor;
     private final TunerService mTunerService;
     private boolean mShowCollapsedOnKeyguard;
-    private final FeatureFlags mFeatureFlags;
 
     @Inject
     public QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader,
             QSPanelController qsPanelController,
             QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost,
             QSSecurityFooter securityFooter, @Main Executor executor, TunerService tunerService,
-            FeatureFlags featureFlags, QSExpansionPathInterpolator qsExpansionPathInterpolator) {
+            QSExpansionPathInterpolator qsExpansionPathInterpolator) {
         mQs = qs;
         mQuickQsPanel = quickPanel;
         mQsPanelController = qsPanelController;
@@ -119,7 +117,6 @@
         mHost = qsTileHost;
         mExecutor = executor;
         mTunerService = tunerService;
-        mFeatureFlags = featureFlags;
         mQSExpansionPathInterpolator = qsExpansionPathInterpolator;
         mHost.addCallback(this);
         mQsPanelController.addOnAttachStateChangeListener(this);
@@ -247,7 +244,6 @@
                 + mQs.getHeader().getPaddingBottom();
         firstPageBuilder.addFloat(tileLayout, "translationY", heightDiff, 0);
 
-        boolean qsSideLabelsEnabled = mFeatureFlags.isQSLabelsEnabled();
         int qqsTileHeight = 0;
 
         if (mQsPanelController.areThereTiles()) {
@@ -275,22 +271,19 @@
                     if (count < tileLayout.getNumVisibleTiles()) {
                         getRelativePosition(loc1, quickTileView, view);
                         getRelativePosition(loc2, tileView, view);
-                        int yOffset = qsSideLabelsEnabled
-                                ? loc2[1] - loc1[1]
-                                : mQuickStatusBarHeader.getOffsetTranslation();
+                        int yOffset = loc2[1] - loc1[1];
                         // Move the quick tile right from its location to the new one.
-                        View v = qsSideLabelsEnabled ? quickTileView.getIcon() : quickTileView;
+                        View v = quickTileView.getIcon();
                         translationXBuilder.addFloat(v, "translationX", 0, xDiff);
                         translationYBuilder.addFloat(v, "translationY", 0, yDiff - yOffset);
                         mAllViews.add(v);
 
                         // Move the real tile from the quick tile position to its final
                         // location.
-                        v = qsSideLabelsEnabled ? tileIcon : tileView;
+                        v = tileIcon;
                         translationXBuilder.addFloat(v, "translationX", -xDiff, 0);
                         translationYBuilder.addFloat(v, "translationY", -yDiff + yOffset, 0);
 
-                    if (qsSideLabelsEnabled) {
                         // Offset the translation animation on the views
                         // (that goes from 0 to getOffsetTranslation)
                         int offsetWithQSBHTranslation =
@@ -300,28 +293,24 @@
                         translationYBuilder.addFloat(tileView, "translationY",
                                 -offsetWithQSBHTranslation, 0);
 
-                            if (mQQSTileHeightAnimator == null) {
-                                mQQSTileHeightAnimator = new HeightExpansionAnimator(this,
-                                        quickTileView.getHeight(), tileView.getHeight());
-                                qqsTileHeight = quickTileView.getHeight();
-                            }
-
-                            mQQSTileHeightAnimator.addView(quickTileView);
-                            View qqsLabelContainer = quickTileView.getLabelContainer();
-                            View qsLabelContainer = tileView.getLabelContainer();
-
-                            getRelativePosition(loc1, qqsLabelContainer, view);
-                            getRelativePosition(loc2, qsLabelContainer, view);
-                            yDiff = loc2[1] - loc1[1] - yOffset;
-
-                            translationYBuilder.addFloat(qqsLabelContainer, "translationY", 0,
-                                    yDiff);
-                            translationYBuilder.addFloat(qsLabelContainer, "translationY", -yDiff,
-                                    0);
-                            mAllViews.add(qqsLabelContainer);
-                            mAllViews.add(qsLabelContainer);
+                        if (mQQSTileHeightAnimator == null) {
+                            mQQSTileHeightAnimator = new HeightExpansionAnimator(this,
+                                    quickTileView.getHeight(), tileView.getHeight());
+                            qqsTileHeight = quickTileView.getHeight();
                         }
 
+                        mQQSTileHeightAnimator.addView(quickTileView);
+                        View qqsLabelContainer = quickTileView.getLabelContainer();
+                        View qsLabelContainer = tileView.getLabelContainer();
+
+                        getRelativePosition(loc1, qqsLabelContainer, view);
+                        getRelativePosition(loc2, qsLabelContainer, view);
+                        yDiff = loc2[1] - loc1[1] - yOffset;
+
+                        translationYBuilder.addFloat(qqsLabelContainer, "translationY", 0, yDiff);
+                        translationYBuilder.addFloat(qsLabelContainer, "translationY", -yDiff, 0);
+                        mAllViews.add(qqsLabelContainer);
+                        mAllViews.add(qsLabelContainer);
                     } else { // These tiles disappear when expanding
                         firstPageBuilder.addFloat(quickTileView, "alpha", 1, 0);
                         translationYBuilder.addFloat(quickTileView, "translationY", 0, yDiff);
@@ -333,11 +322,7 @@
                                 translationX);
                     }
 
-                    if (qsSideLabelsEnabled) {
-                        mQuickQsViews.add(tileView);
-                    } else {
-                        mQuickQsViews.add(tileView.getIconWithBackground());
-                    }
+                    mQuickQsViews.add(tileView);
                     mAllViews.add(tileView.getIcon());
                     mAllViews.add(quickTileView);
                 } else if (mFullRows && isIconInAnimatedRow(count)) {
@@ -346,27 +331,22 @@
 
                     mAllViews.add(tileIcon);
                 } else {
-                    if (!qsSideLabelsEnabled) {
-                        firstPageBuilder.addFloat(tileView, "alpha", 0, 1);
-                        firstPageBuilder.addFloat(tileView, "translationY", -heightDiff, 0);
-                    } else {
-                        // Pretend there's a corresponding QQS tile (for the position) that we are
-                        // expanding from.
-                        SideLabelTileLayout qqsLayout =
-                                (SideLabelTileLayout) mQuickQsPanel.getTileLayout();
-                        getRelativePosition(loc1, qqsLayout, view);
-                        getRelativePosition(loc2, tileView, view);
-                        int diff = loc2[1] - (loc1[1] + qqsLayout.getPhantomTopPosition(count));
-                        translationYBuilder.addFloat(tileView, "translationY", -diff, 0);
-                        if (mOtherTilesExpandAnimator == null) {
-                            mOtherTilesExpandAnimator =
-                                    new HeightExpansionAnimator(
-                                            this, qqsTileHeight, tileView.getHeight());
-                        }
-                        mOtherTilesExpandAnimator.addView(tileView);
-                        tileView.setClipChildren(true);
-                        tileView.setClipToPadding(true);
+                    // Pretend there's a corresponding QQS tile (for the position) that we are
+                    // expanding from.
+                    SideLabelTileLayout qqsLayout =
+                            (SideLabelTileLayout) mQuickQsPanel.getTileLayout();
+                    getRelativePosition(loc1, qqsLayout, view);
+                    getRelativePosition(loc2, tileView, view);
+                    int diff = loc2[1] - (loc1[1] + qqsLayout.getPhantomTopPosition(count));
+                    translationYBuilder.addFloat(tileView, "translationY", -diff, 0);
+                    if (mOtherTilesExpandAnimator == null) {
+                        mOtherTilesExpandAnimator =
+                                new HeightExpansionAnimator(
+                                        this, qqsTileHeight, tileView.getHeight());
                     }
+                    mOtherTilesExpandAnimator.addView(tileView);
+                    tileView.setClipChildren(true);
+                    tileView.setClipToPadding(true);
                 }
 
                 mAllViews.add(tileView);
@@ -392,7 +372,6 @@
                     .build();
             // Fade in the tiles/labels as we reach the final position.
             Builder builder = new Builder()
-                    .setStartDelay(qsSideLabelsEnabled ? 0 : EXPANDED_TILE_DELAY)
                     .addFloat(tileLayout, "alpha", 0, 1);
             mFirstPageDelayedAnimator = builder.build();
 
@@ -470,12 +449,7 @@
 
     // Returns true if the view is a possible page in PagedTileLayout
     private boolean isAPage(View view) {
-        if (view instanceof PagedTileLayout.TilePage) {
-            return true;
-        } else if (view instanceof SideLabelTileLayout) {
-            return !(view instanceof QuickQSPanel.QQSSideLabelTileLayout);
-        }
-        return false;
+        return view.getClass().equals(SideLabelTileLayout.class);
     }
 
     public void setPosition(float position) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 586176f..bf9acc2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -22,7 +22,6 @@
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.util.AttributeSet;
-import android.util.Pair;
 import android.view.View;
 import android.view.WindowInsets;
 import android.widget.FrameLayout;
@@ -61,7 +60,6 @@
     private QuickStatusBarHeader mHeader;
     private float mQsExpansion;
     private QSCustomizer mQSCustomizer;
-    private View mDragHandle;
     private NonInterceptingScrollView mQSPanelContainer;
 
     private View mBackground;
@@ -84,7 +82,6 @@
         mQSDetail = findViewById(R.id.qs_detail);
         mHeader = findViewById(R.id.header);
         mQSCustomizer = findViewById(R.id.qs_customize);
-        mDragHandle = findViewById(R.id.qs_drag_handle_view);
         mBackground = findViewById(R.id.quick_settings_background);
         mHeader.getHeaderQsPanel().setMediaVisibilityChangedListener((visible) -> {
             if (mHeader.getHeaderQsPanel().isShown()) {
@@ -240,8 +237,6 @@
         int scrollBottom = calculateContainerBottom();
         setBottom(getTop() + height);
         mQSDetail.setBottom(getTop() + scrollBottom);
-        // Pin the drag handle to the bottom of the panel.
-        mDragHandle.setTranslationY(scrollBottom - mDragHandle.getHeight());
         mBackground.setTop(mQSPanelContainer.getTop());
         updateBackgroundBottom(scrollBottom, animate);
     }
@@ -278,7 +273,6 @@
 
     public void setExpansion(float expansion) {
         mQsExpansion = expansion;
-        mDragHandle.setAlpha(1.0f - expansion);
         updateExpansion();
     }
 
@@ -296,9 +290,6 @@
             if (view == mQSPanelContainer) {
                 // QS panel lays out some of its content full width
                 qsPanelController.setContentMargins(mContentPadding, mContentPadding);
-                Pair<Integer, Integer> margins = qsPanelController.getVisualSideMargins();
-                // Apply paddings based on QSPanel
-                mQSCustomizer.setContentPaddings(margins.first, margins.second);
             } else if (view == mHeader) {
                 // The header contains the QQS panel which needs to have special padding, to
                 // visually align them.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
index eb7b115..3718713 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
@@ -71,7 +71,6 @@
     private float mExpansionAmount;
 
     protected View mEdit;
-    protected View mEditContainer;
     private TouchAnimator mSettingsCogAnimator;
 
     private View mActionsContainer;
@@ -107,7 +106,6 @@
         mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
 
         mActionsContainer = requireViewById(R.id.qs_footer_actions_container);
-        mEditContainer = findViewById(R.id.qs_footer_actions_edit_container);
         mBuildText = findViewById(R.id.build);
         mTunerIcon = requireViewById(R.id.tuner_icon);
 
@@ -185,9 +183,6 @@
                 .addFloat(mPageIndicator, "alpha", 0, 1)
                 .addFloat(mBuildText, "alpha", 0, 1)
                 .setStartDelay(0.9f);
-        if (mEditContainer != null) {
-            builder.addFloat(mEditContainer, "alpha", 0, 1);
-        }
         return builder.build();
     }
 
@@ -283,9 +278,6 @@
         mTunerIcon.setVisibility(isTunerEnabled ? View.VISIBLE : View.INVISIBLE);
         final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
         mMultiUserSwitch.setVisibility(showUserSwitcher() ? View.VISIBLE : View.GONE);
-        if (mEditContainer != null) {
-            mEditContainer.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
-        }
         mSettingsButton.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
 
         mBuildText.setVisibility(mExpanded && mShouldShowBuildText ? View.VISIBLE : View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 27cc268..f89e70a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -27,12 +27,10 @@
 import android.os.Handler;
 import android.os.Message;
 import android.util.AttributeSet;
-import android.util.Pair;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewStub;
 import android.widget.LinearLayout;
 
 import com.android.internal.logging.UiEventLogger;
@@ -72,8 +70,6 @@
     private final H mHandler = new H();
     /** Whether or not the QS media player feature is enabled. */
     protected boolean mUsingMediaPlayer;
-    private int mVisualMarginStart;
-    private int mVisualMarginEnd;
 
     protected boolean mExpanded;
     protected boolean mListening;
@@ -96,7 +92,6 @@
     private PageIndicator mFooterPageIndicator;
     private int mContentMarginStart;
     private int mContentMarginEnd;
-    private int mVisualTilePadding;
     private boolean mUsingHorizontalLayout;
 
     private Record mDetailRecord;
@@ -111,9 +106,7 @@
     protected QSTileLayout mTileLayout;
     private int mLastOrientation = -1;
     private int mMediaTotalBottomMargin;
-    private int mFooterMarginStartHorizontal;
     private Consumer<Boolean> mMediaVisibilityChangedListener;
-    protected boolean mSideLabels;
 
     public QSPanel(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -128,21 +121,7 @@
 
     }
 
-    protected void inflateQSFooter(boolean newFooter) {
-        ViewStub stub = findViewById(R.id.qs_footer_stub);
-        if (stub != null) {
-            stub.setLayoutResource(
-                    newFooter ? R.layout.qs_footer_impl_two_lines : R.layout.qs_footer_impl);
-            stub.inflate();
-            mFooter = findViewById(R.id.qs_footer);
-        }
-    }
-
-    void initialize(boolean sideLabels) {
-        mSideLabels = sideLabels;
-
-        inflateQSFooter(sideLabels);
-
+    void initialize() {
         mRegularTileLayout = createRegularTileLayout();
         mTileLayout = mRegularTileLayout;
 
@@ -195,8 +174,7 @@
     public QSTileLayout createRegularTileLayout() {
         if (mRegularTileLayout == null) {
             mRegularTileLayout = (QSTileLayout) LayoutInflater.from(mContext)
-                    .inflate(mSideLabels ? R.layout.qs_paged_tile_layout_side_labels
-                            : R.layout.qs_paged_tile_layout, this, false);
+                    .inflate(R.layout.qs_paged_tile_layout, this, false);
         }
         return mRegularTileLayout;
     }
@@ -311,11 +289,6 @@
     }
 
     public void updateResources() {
-        int tileSize = getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size);
-        int tileBg = getResources().getDimensionPixelSize(R.dimen.qs_tile_background_size);
-        mFooterMarginStartHorizontal = getResources().getDimensionPixelSize(
-                R.dimen.qs_footer_horizontal_margin);
-        mVisualTilePadding = mSideLabels ? 0 : (int) ((tileSize - tileBg) / 2.0f);
         updatePadding();
 
         updatePageIndicator();
@@ -358,6 +331,7 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+        mFooter = findViewById(R.id.qs_footer);
         mDivider = findViewById(R.id.divider);
     }
 
@@ -638,60 +612,10 @@
         // to the edge like the brightness slider
         mContentMarginStart = startMargin;
         mContentMarginEnd = endMargin;
-        updateTileLayoutMargins(mContentMarginStart - mVisualTilePadding,
-                mContentMarginEnd - mVisualTilePadding);
         updateMediaHostContentMargins(mediaHostView);
-        updateFooterMargin();
         updateDividerMargin();
     }
 
-    private void updateFooterMargin() {
-        if (mFooter != null) {
-            int footerMargin = 0;
-            int indicatorMargin = 0;
-            if (mUsingHorizontalLayout && !mSideLabels) {
-                footerMargin = mFooterMarginStartHorizontal;
-                indicatorMargin = footerMargin - mVisualMarginEnd;
-            }
-            updateMargins(mFooter, footerMargin, 0);
-            // The page indicator isn't centered anymore because of the visual positioning.
-            // Let's fix it by adding some margin
-            if (mFooterPageIndicator != null) {
-                updateMargins(mFooterPageIndicator, 0, indicatorMargin);
-            }
-        }
-    }
-
-    /**
-     * Update the margins of all tile Layouts.
-     *
-     * @param visualMarginStart the visual start margin of the tile, adjusted for local insets
-     *                          to the tile. This can be set on a tileLayout
-     * @param visualMarginEnd the visual end margin of the tile, adjusted for local insets
-     *                        to the tile. This can be set on a tileLayout
-     */
-    private void updateTileLayoutMargins(int visualMarginStart, int visualMarginEnd) {
-        mVisualMarginStart = visualMarginStart;
-        mVisualMarginEnd = visualMarginEnd;
-        updateTileLayoutMargins();
-    }
-
-    public Pair<Integer, Integer> getVisualSideMargins() {
-        if (mSideLabels) {
-            return new Pair(0, 0);
-        } else {
-            return new Pair(mVisualMarginStart, mUsingHorizontalLayout ? 0 : mVisualMarginEnd);
-        }
-    }
-
-    private void updateTileLayoutMargins() {
-        int marginEnd = mVisualMarginEnd;
-        if (mUsingHorizontalLayout || mSideLabels) {
-            marginEnd = 0;
-        }
-        updateMargins((View) mTileLayout, mSideLabels ? 0 : mVisualMarginStart, marginEnd);
-    }
-
     private void updateDividerMargin() {
         if (mDivider == null) return;
         updateMargins(mDivider, mContentMarginStart, mContentMarginEnd);
@@ -769,22 +693,13 @@
             newLayout.setListening(mListening, uiEventLogger);
             if (needsDynamicRowsAndColumns()) {
                 newLayout.setMinRows(horizontal ? 2 : 1);
-                // Let's use 3 columns to match the current layout
-                int columns;
-                if (mSideLabels) {
-                    columns = horizontal ? 2 : 4;
-                } else {
-                    columns = horizontal ? 3 : TileLayout.NO_MAX_COLUMNS;
-                }
-                newLayout.setMaxColumns(columns);
+                newLayout.setMaxColumns(horizontal ? 2 : 4);
             }
             updateMargins(mediaHostView);
         }
     }
 
     private void updateMargins(ViewGroup mediaHostView) {
-        updateTileLayoutMargins();
-        updateFooterMargin();
         updateDividerMargin();
         updateMediaHostContentMargins(mediaHostView);
         updateHorizontalLinearLayoutMargins();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index eda1abb..5b6b5df 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -22,7 +22,6 @@
 
 import android.annotation.NonNull;
 import android.content.res.Configuration;
-import android.util.Pair;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -291,11 +290,6 @@
     }
 
     /** */
-    public Pair<Integer, Integer> getVisualSideMargins() {
-        return mView.getVisualSideMargins();
-    }
-
-    /** */
     public void showDetailAdapter(DetailAdapter detailAdapter, int x, int y) {
         mView.showDetailAdapter(true, detailAdapter, new int[]{x, y});
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index e41a038..925c9eb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -75,8 +75,6 @@
     private float mRevealExpansion;
 
     private final QSHost.Callback mQSHostCallback = this::setTiles;
-    protected boolean mShowLabels = true;
-    protected boolean mQSLabelFlag;
 
     private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
             new QSPanel.OnConfigurationChangedListener() {
@@ -121,14 +119,13 @@
         mQSLogger = qsLogger;
         mDumpManager = dumpManager;
         mFeatureFlags = featureFlags;
-        mQSLabelFlag = featureFlags.isQSLabelsEnabled();
         mShouldUseSplitNotificationShade =
                 Utils.shouldUseSplitNotificationShade(mFeatureFlags, getResources());
     }
 
     @Override
     protected void onInit() {
-        mView.initialize(mQSLabelFlag);
+        mView.initialize();
         mQSLogger.logAllTilesChangeListening(mView.isListening(), mView.getDumpableTag(), "");
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index e7828c3..63733b3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -50,16 +50,11 @@
     }
 
     @Override
-    void initialize(boolean sideLabels) {
-        super.initialize(sideLabels);
+    void initialize() {
+        super.initialize();
         applyBottomMargin((View) mRegularTileLayout);
     }
 
-    @Override
-    protected void inflateQSFooter(boolean newFooter) {
-        // No footer
-    }
-
     private void applyBottomMargin(View view) {
         int margin = getResources().getDimensionPixelSize(R.dimen.qs_header_tile_margin_bottom);
         MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
@@ -74,22 +69,14 @@
 
     @Override
     public TileLayout createRegularTileLayout() {
-        if (mSideLabels) {
-            return new QQSSideLabelTileLayout(mContext);
-        } else {
-            return new QuickQSPanel.HeaderTileLayout(mContext);
-        }
+        return new QQSSideLabelTileLayout(mContext);
     }
 
     @Override
     protected QSTileLayout createHorizontalTileLayout() {
-        if (mSideLabels) {
-            TileLayout t = createRegularTileLayout();
-            t.setMaxColumns(2);
-            return t;
-        } else {
-            return new DoubleLineTileLayout(mContext);
-        }
+        TileLayout t = createRegularTileLayout();
+        t.setMaxColumns(2);
+        return t;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 30a08c6..7518b20 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -141,19 +141,6 @@
         }
     }
 
-    /**
-     * Sets the padding for the RecyclerView. Also, updates the margin between the tiles in the
-     * {@link TileAdapter}.
-     */
-    public void setContentPaddings(int paddingStart, int paddingEnd) {
-        mRecyclerView.setPaddingRelative(
-                paddingStart,
-                mRecyclerView.getPaddingTop(),
-                paddingEnd,
-                mRecyclerView.getPaddingBottom()
-        );
-    }
-
     /** Hide the customizer. */
     public void hide(boolean animate) {
         if (isShown) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 006b230..5080533 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -14,8 +14,6 @@
 
 package com.android.systemui.qs.customize;
 
-import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG;
-
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
@@ -61,7 +59,6 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 
 /** */
 @QSScope
@@ -110,25 +107,21 @@
     private final AccessibilityDelegateCompat mAccessibilityDelegate;
     private RecyclerView mRecyclerView;
     private int mNumColumns;
-    private final boolean mUseHorizontalTiles;
 
     @Inject
     public TileAdapter(
             @QSThemedContext Context context,
             QSTileHost qsHost,
-            UiEventLogger uiEventLogger,
-            @Named(QS_LABELS_FLAG) boolean useHorizontalTiles
-    ) {
+            UiEventLogger uiEventLogger) {
         mContext = context;
         mHost = qsHost;
         mUiEventLogger = uiEventLogger;
         mItemTouchHelper = new ItemTouchHelper(mCallbacks);
         mDecoration = new TileItemDecoration(context);
-        mMarginDecoration = new MarginTileDecoration(!useHorizontalTiles);
+        mMarginDecoration = new MarginTileDecoration();
         mMinNumTiles = context.getResources().getInteger(R.integer.quick_settings_min_num_tiles);
         mNumColumns = context.getResources().getInteger(NUM_COLUMNS_ID);
         mAccessibilityDelegate = new TileAdapterDelegate();
-        mUseHorizontalTiles = useHorizontalTiles;
         mSizeLookup.setSpanIndexCacheEnabled(true);
     }
 
@@ -287,9 +280,7 @@
         }
         FrameLayout frame = (FrameLayout) inflater.inflate(R.layout.qs_customize_tile_frame, parent,
                 false);
-        View view = mUseHorizontalTiles
-                ? new CustomizeTileViewHorizontal(context, new QSIconViewImpl(context))
-                : new CustomizeTileView(context, new QSIconViewImpl(context));
+        View view = new CustomizeTileViewHorizontal(context, new QSIconViewImpl(context));
         frame.addView(view);
         return new Holder(frame);
     }
@@ -715,11 +706,6 @@
 
     private static class MarginTileDecoration extends ItemDecoration {
         private int mHalfMargin;
-        private final boolean mUseOutsideMargins;
-
-        private MarginTileDecoration(boolean useOutsideMargins) {
-            mUseOutsideMargins = useOutsideMargins;
-        }
 
         public void setHalfMargin(int halfMargin) {
             mHalfMargin = halfMargin;
@@ -738,9 +724,9 @@
             if (view instanceof TextView) {
                 super.getItemOffsets(outRect, view, parent, state);
             } else {
-                if (mUseOutsideMargins || (column != 0 && column != lm.getSpanCount() - 1)) {
-                    // Using outside margins or in a column that's not leftmost or rightmost
-                    // (half of the margin between columns).
+                if (column != 0 && column != lm.getSpanCount() - 1) {
+                    // In a column that's not leftmost or rightmost (half of the margin between
+                    // columns).
                     outRect.left = mHalfMargin;
                     outRect.right = mHalfMargin;
                 } else if (column == 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java
index 10192bc..a1e1d64 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java
@@ -30,18 +30,11 @@
 
 @Module
 public interface QSFlagsModule {
-    String QS_LABELS_FLAG = "qs_labels_flag";
+
     String RBC_AVAILABLE = "rbc_available";
     String PM_LITE_ENABLED = "pm_lite";
     String PM_LITE_SETTING = "sysui_pm_lite";
 
-    @Provides
-    @SysUISingleton
-    @Named(QS_LABELS_FLAG)
-    static boolean provideQSFlag(FeatureFlags featureFlags) {
-        return featureFlags.isQSLabelsEnabled();
-    }
-
     /** */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 9b0536c..3437dd5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -14,8 +14,6 @@
 
 package com.android.systemui.qs.tileimpl;
 
-import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG;
-
 import android.content.Context;
 import android.os.Build;
 import android.util.Log;
@@ -56,7 +54,6 @@
 import com.android.systemui.util.leak.GarbageMonitor;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Provider;
 
 import dagger.Lazy;
@@ -97,12 +94,9 @@
     private final Lazy<QSHost> mQsHostLazy;
     private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
 
-    private final boolean mSideLabels;
-
     @Inject
     public QSFactoryImpl(
             Lazy<QSHost> qsHostLazy,
-            @Named(QS_LABELS_FLAG) boolean useSideLabels,
             Provider<CustomTile.Builder> customTileBuilderProvider,
             Provider<WifiTile> wifiTileProvider,
             Provider<InternetTile> internetTileProvider,
@@ -134,8 +128,6 @@
         mQsHostLazy = qsHostLazy;
         mCustomTileBuilderProvider = customTileBuilderProvider;
 
-        mSideLabels = useSideLabels;
-
         mWifiTileProvider = wifiTileProvider;
         mInternetTileProvider = internetTileProvider;
         mBluetoothTileProvider = bluetoothTileProvider;
@@ -251,12 +243,6 @@
     @Override
     public QSTileView createTileView(Context context, QSTile tile, boolean collapsedView) {
         QSIconView icon = tile.createTileView(context);
-        if (mSideLabels) {
-            return new QSTileViewHorizontal(context, icon, collapsedView);
-        } else if (collapsedView) {
-            return new QSTileBaseView(context, icon, collapsedView);
-        } else {
-            return new com.android.systemui.qs.tileimpl.QSTileView(context, icon);
-        }
+        return new QSTileViewHorizontal(context, icon, collapsedView);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index aa8ce85..ba69dd5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -66,9 +66,9 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTile.State;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.PagedTileLayout.TilePage;
 import com.android.systemui.qs.QSEvent;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.SideLabelTileLayout;
 import com.android.systemui.qs.logging.QSLogger;
 
 import java.io.FileDescriptor;
@@ -497,7 +497,7 @@
 
     private void updateIsFullQs() {
         for (Object listener : mListeners) {
-            if (TilePage.class.equals(listener.getClass())) {
+            if (SideLabelTileLayout.class.equals(listener.getClass())) {
                 mIsFullQs = 1;
                 return;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
index dbd6758..b60fd13 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -48,7 +48,7 @@
         super(context, attrs);
     }
 
-    // Inflated from quick_settings_brightness_dialog or quick_settings_brightness_dialog_thick
+    // Inflated from quick_settings_brightness_dialog
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index ec3a857..17b489c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -53,11 +53,6 @@
         return mFlagReader.isEnabled(R.bool.flag_notification_twocolumn);
     }
 
-    // Does not support runtime changes
-    public boolean isQSLabelsEnabled() {
-        return mFlagReader.isEnabled(R.bool.flag_qs_labels);
-    }
-
     public boolean isKeyguardLayoutEnabled() {
         return mFlagReader.isEnabled(R.bool.flag_keyguard_layout);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index f57fd21..f4266a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -41,6 +41,7 @@
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
 import com.android.systemui.statusbar.notification.stack.ViewState;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
 
@@ -82,6 +83,9 @@
     private Rect mClipRect = new Rect();
     private int mCutoutHeight;
     private int mGapHeight;
+    private int mIndexOfFirstViewInShelf = -1;
+    private int mIndexOfFirstViewInOverflowingSection = -1;
+
     private NotificationShelfController mController;
 
     public NotificationShelf(Context context, AttributeSet attrs) {
@@ -159,30 +163,49 @@
     }
 
     /** Update the state of the shelf. */
-    public void updateState(AmbientState ambientState) {
+    public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
+            AmbientState ambientState) {
         ExpandableView lastView = ambientState.getLastVisibleBackgroundChild();
         ShelfState viewState = (ShelfState) getViewState();
         if (mShowNotificationShelf && lastView != null) {
-            float maxShelfEnd = ambientState.getInnerHeight() + ambientState.getTopPadding()
-                    + ambientState.getStackTranslation();
             ExpandableViewState lastViewState = lastView.getViewState();
-            float viewEnd = lastViewState.yTranslation + lastViewState.height;
             viewState.copyFrom(lastViewState);
+
             viewState.height = getIntrinsicHeight();
-            viewState.yTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - viewState.height,
-                    getFullyClosedTranslation());
             viewState.zTranslation = ambientState.getBaseZHeight();
             viewState.clipTopAmount = 0;
             viewState.alpha = 1f - ambientState.getHideAmount();
             viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0;
             viewState.hideSensitive = false;
             viewState.xTranslation = getTranslationX();
+            viewState.hasItemsInStableShelf = lastViewState.inShelf;
+            viewState.firstViewInShelf = algorithmState.firstViewInShelf;
+            viewState.firstViewInOverflowSection = algorithmState.firstViewInOverflowSection;
             if (mNotGoneIndex != -1) {
                 viewState.notGoneIndex = Math.min(viewState.notGoneIndex, mNotGoneIndex);
             }
-            viewState.hasItemsInStableShelf = lastViewState.inShelf;
+
             viewState.hidden = !mAmbientState.isShadeExpanded()
-                    || mAmbientState.isQsCustomizerShowing();
+                    || mAmbientState.isQsCustomizerShowing()
+                    || algorithmState.firstViewInShelf == null;
+
+            final int indexOfFirstViewInShelf = algorithmState.visibleChildren.indexOf(
+                    algorithmState.firstViewInShelf);
+
+            if (mAmbientState.isExpansionChanging()
+                    && algorithmState.firstViewInShelf != null
+                    && indexOfFirstViewInShelf > 0) {
+
+                // Show shelf if section before it is showing.
+                final ExpandableView viewBeforeShelf = algorithmState.visibleChildren.get(
+                        indexOfFirstViewInShelf - 1);
+                if (viewBeforeShelf.getViewState().hidden) {
+                    viewState.hidden = true;
+                }
+            }
+
+            final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
+            viewState.yTranslation = stackEnd - viewState.height;
         } else {
             viewState.hidden = true;
             viewState.location = ExpandableViewState.LOCATION_GONE;
@@ -199,13 +222,11 @@
         if (!mShowNotificationShelf) {
             return;
         }
-
         mShelfIcons.resetViewStates();
         float shelfStart = getTranslationY();
         float numViewsInShelf = 0.0f;
         View lastChild = mAmbientState.getLastVisibleBackgroundChild();
         mNotGoneIndex = -1;
-        float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2;
         //  find the first view that doesn't overlap with the shelf
         int notGoneIndex = 0;
         int colorOfViewBeforeLast = NO_COLOR;
@@ -219,7 +240,7 @@
         float currentScrollVelocity = mAmbientState.getCurrentScrollVelocity();
         boolean scrollingFast = currentScrollVelocity > mScrollFastThreshold
                 || (mAmbientState.isExpansionChanging()
-                        && Math.abs(mAmbientState.getExpandingVelocity()) > mScrollFastThreshold);
+                && Math.abs(mAmbientState.getExpandingVelocity()) > mScrollFastThreshold);
         boolean expandingAnimated = mAmbientState.isExpansionChanging()
                 && !mAmbientState.isPanelTracking();
         int baseZHeight = mAmbientState.getBaseZHeight();
@@ -233,22 +254,37 @@
             if (!child.needsClippingToShelf() || child.getVisibility() == GONE) {
                 continue;
             }
-
             float notificationClipEnd;
             boolean aboveShelf = ViewState.getFinalTranslationZ(child) > baseZHeight
                     || child.isPinned();
             boolean isLastChild = child == lastChild;
             float rowTranslationY = child.getTranslationY();
+
+            final float inShelfAmount = updateShelfTransformation(i, child, scrollingFast,
+                    expandingAnimated, isLastChild);
+
+            final float stackEnd = mAmbientState.getStackY()
+                    + mAmbientState.getStackHeight();
+            // TODO(b/172289889) scale mPaddingBetweenElements with expansion amount
             if ((isLastChild && !child.isInShelf()) || aboveShelf || backgroundForceHidden) {
-                notificationClipEnd = shelfStart + getIntrinsicHeight();
+                notificationClipEnd = stackEnd;
+            } else if (mAmbientState.isExpansionChanging()) {
+                if (mIndexOfFirstViewInOverflowingSection != -1
+                    && i >= mIndexOfFirstViewInOverflowingSection) {
+                    // Clip notifications in (section overflowing into shelf) to shelf start.
+                    notificationClipEnd = shelfStart - mPaddingBetweenElements;
+                } else {
+                    // Clip notifications before the section overflowing into shelf
+                    // to stackEnd because we do not show the shelf if the section right before the
+                    // shelf is still hidden.
+                    notificationClipEnd = stackEnd;
+                }
             } else {
                 notificationClipEnd = shelfStart - mPaddingBetweenElements;
             }
             int clipTop = updateNotificationClipHeight(child, notificationClipEnd, notGoneIndex);
             clipTopAmount = Math.max(clipTop, clipTopAmount);
 
-            final float inShelfAmount = updateShelfTransformation(child, scrollingFast,
-                    expandingAnimated, isLastChild);
             // If the current row is an ExpandableNotificationRow, update its color, roundedness,
             // and icon state.
             if (child instanceof ExpandableNotificationRow) {
@@ -314,19 +350,23 @@
                                 distanceToGapTop / mGapHeight);
                         previousAnv.setBottomRoundness(firstElementRoundness,
                                 false /* don't animate */);
-                        backgroundTop = (int) distanceToGapBottom;
                     }
                 }
                 previousAnv = anv;
             }
         }
+
         clipTransientViews();
 
         setClipTopAmount(clipTopAmount);
-        boolean isHidden = getViewState().hidden || clipTopAmount >= getIntrinsicHeight();
-        if (mShowNotificationShelf) {
-            setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
-        }
+
+        boolean isHidden = getViewState().hidden
+                || clipTopAmount >= getIntrinsicHeight()
+                || !mShowNotificationShelf
+                || numViewsInShelf < 1f;
+
+        // TODO(b/172289889) transition last icon in shelf to notification icon and vice versa.
+        setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
         setBackgroundTop(backgroundTop);
         setFirstElementRoundness(firstElementRoundness);
         mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex());
@@ -339,11 +379,10 @@
                 continue;
             }
             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-            updateIconClipAmount(row);
             updateContinuousClipping(row);
         }
-        boolean hideBackground = numViewsInShelf < 1.0f;
-        setHideBackground(hideBackground || backgroundForceHidden);
+        boolean hideBackground = isHidden;
+        setHideBackground(hideBackground);
         if (mNotGoneIndex == -1) {
             mNotGoneIndex = notGoneIndex;
         }
@@ -476,10 +515,10 @@
     /**
      * @return the amount how much this notification is in the shelf
      */
-    private float updateShelfTransformation(ExpandableView view, boolean scrollingFast,
+    private float updateShelfTransformation(int i, ExpandableView view, boolean scrollingFast,
             boolean expandingAnimated, boolean isLastChild) {
 
-        // Let calculate how much the view is in the shelf
+        // Let's calculate how much the view is in the shelf
         float viewStart = view.getTranslationY();
         int fullHeight = view.getActualHeight() + mPaddingBetweenElements;
         float iconTransformStart = calculateIconTransformationStart(view);
@@ -496,15 +535,21 @@
                     transformDistance,
                     view.getMinHeight() - getIntrinsicHeight());
         }
+
         float viewEnd = viewStart + fullHeight;
         float fullTransitionAmount = 0.0f;
         float iconTransitionAmount = 0.0f;
         float shelfStart = getTranslationY();
-
-        if (viewEnd >= shelfStart
+        if (mAmbientState.isExpansionChanging() && !mAmbientState.isOnKeyguard()) {
+            // TODO(b/172289889) handle icon placement for notification that is clipped by the shelf
+            if (mIndexOfFirstViewInShelf != -1 && i >= mIndexOfFirstViewInShelf) {
+                fullTransitionAmount = 1f;
+                iconTransitionAmount = 1f;
+            }
+        } else if (viewEnd >= shelfStart
                 && (!mAmbientState.isUnlockHintRunning() || view.isInShelf())
                 && (mAmbientState.isShadeExpanded()
-                        || (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) {
+                || (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) {
 
             if (viewStart < shelfStart) {
                 float fullAmount = (shelfStart - viewStart) / fullHeight;
@@ -572,7 +617,7 @@
                     && !mNoAnimationsInThisFrame;
         }
         iconState.clampedAppearAmount = clampedAmount;
-        setIconTransformationAmount(view, transitionAmount, isLastChild);
+        setIconTransformationAmount(view, transitionAmount);
     }
 
     private boolean isTargetClipped(ExpandableView view) {
@@ -585,12 +630,10 @@
                 + view.getContentTranslation()
                 + view.getRelativeTopPadding(target)
                 + target.getHeight();
-
         return endOfTarget >= getTranslationY() - mPaddingBetweenElements;
     }
 
-    private void setIconTransformationAmount(ExpandableView view, float transitionAmount,
-            boolean isLastChild) {
+    private void setIconTransformationAmount(ExpandableView view, float transitionAmount) {
         if (!(view instanceof ExpandableNotificationRow)) {
             return;
         }
@@ -601,7 +644,6 @@
             return;
         }
         iconState.alpha = transitionAmount;
-
         boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf();
         iconState.hidden = isAppearing
                 || (view instanceof ExpandableNotificationRow
@@ -610,8 +652,8 @@
                 || (transitionAmount == 0.0f && !iconState.isAnimating(icon))
                 || row.isAboveShelf()
                 || row.showingPulsing()
-                || (!row.isInShelf() && isLastChild)
                 || row.getTranslationZ() > mAmbientState.getBaseZHeight();
+
         iconState.iconAppearAmount = iconState.hidden? 0f : transitionAmount;
 
         // Fade in icons at shelf start
@@ -790,8 +832,19 @@
         mController = notificationShelfController;
     }
 
+    public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) {
+        mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
+    }
+
+    public void setFirstViewInOverflowingSection(ExpandableView firstViewInOverflowingSection) {
+        mIndexOfFirstViewInOverflowingSection =
+                mHostLayoutController.indexOfChild(firstViewInOverflowingSection);
+    }
+
     private class ShelfState extends ExpandableViewState {
         private boolean hasItemsInStableShelf;
+        private ExpandableView firstViewInShelf;
+        private ExpandableView firstViewInOverflowSection;
 
         @Override
         public void applyToView(View view) {
@@ -800,6 +853,8 @@
             }
 
             super.applyToView(view);
+            setIndexOfFirstViewInShelf(firstViewInShelf);
+            setFirstViewInOverflowingSection(firstViewInOverflowSection);
             updateAppearance();
             setHasItemsInStableShelf(hasItemsInStableShelf);
             mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
@@ -812,6 +867,8 @@
             }
 
             super.animateTo(child, properties);
+            setIndexOfFirstViewInShelf(firstViewInShelf);
+            setFirstViewInOverflowingSection(firstViewInOverflowSection);
             updateAppearance();
             setHasItemsInStableShelf(hasItemsInStableShelf);
             mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
index 1e935c1..4f70fdb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
@@ -22,6 +22,7 @@
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
 import com.android.systemui.statusbar.phone.StatusBarNotificationPresenter;
@@ -103,9 +104,10 @@
         return mView.getHeight();
     }
 
-    public void updateState(AmbientState ambientState) {
+    public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
+            AmbientState ambientState) {
         mAmbientState = ambientState;
-        mView.updateState(ambientState);
+        mView.updateState(algorithmState, ambientState);
     }
 
     public int getIntrinsicHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
index c811fdd..a537299 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
@@ -25,12 +25,14 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Looper;
 import android.util.AttributeSet;
 import android.view.View;
 
 import androidx.annotation.DimenRes;
+import androidx.annotation.Nullable;
 import androidx.core.graphics.ColorUtils;
 
 import com.android.internal.annotations.GuardedBy;
@@ -66,6 +68,8 @@
     private Executor mChangeRunnableExecutor;
     private Executor mExecutor;
     private Looper mExecutorLooper;
+    @Nullable
+    private Rect mDrawableBounds;
 
     public ScrimView(Context context) {
         this(context, null);
@@ -125,7 +129,9 @@
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
-        if (changed) {
+        if (mDrawableBounds != null) {
+            mDrawable.setBounds(mDrawableBounds);
+        } else if (changed) {
             mDrawable.setBounds(left, top, right, bottom);
             invalidate();
         }
@@ -288,4 +294,15 @@
             ((ScrimDrawable) mDrawable).setRoundedCorners(radius);
         }
     }
+
+    /**
+     * Set bounds for the view, all coordinates are absolute
+     */
+    public void setDrawableBounds(float left, float top, float right, float bottom) {
+        if (mDrawableBounds == null) {
+            mDrawableBounds = new Rect();
+        }
+        mDrawableBounds.set((int) left, (int) top, (int) right, (int) bottom);
+        mDrawable.setBounds(mDrawableBounds);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
new file mode 100644
index 0000000..5ab71bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ObjectAnimator
+import android.annotation.UiThread
+import android.util.Log
+import android.view.Gravity
+import android.view.View
+import android.widget.FrameLayout
+
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+
+import java.lang.IllegalStateException
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Understands how to keep the persistent privacy dot in the corner of the screen in
+ * ScreenDecorations, which does not rotate with the device.
+ *
+ * The basic principle here is that each dot will sit in a box that is equal to the margins of the
+ * status bar (specifically the status_bar_contents view in PhoneStatusBarView). Each dot container
+ * will have its gravity set towards the corner (i.e., top-right corner gets top|right gravity), and
+ * the contained ImageView will be set to center_vertical and away from the corner horizontally. The
+ * Views will match the status bar top padding and status bar height so that the dot can appear to
+ * reside directly after the status bar system contents (basically to the right of the battery).
+ *
+ * NOTE: any operation that modifies views directly must run on the provided executor, because
+ * these views are owned by ScreenDecorations and it runs in its own thread
+ */
+
+@SysUISingleton
+class PrivacyDotViewController @Inject constructor(
+    @Main val mainExecutor: Executor,
+    val animationScheduler: SystemStatusAnimationScheduler
+) {
+    private var rotation = 0
+    private var leftSize = 0
+    private var rightSize = 0
+
+    private var sbHeightPortrait = 0
+    private var sbHeightLandscape = 0
+
+    private var hasMultipleHeights = false
+    private var needsHeightUpdate = false
+    private var needsRotationUpdate = false
+    private var needsMarginUpdate = false
+
+    private lateinit var tl: View
+    private lateinit var tr: View
+    private lateinit var bl: View
+    private lateinit var br: View
+
+    // Track which corner is active (based on orientation + RTL)
+    private var designatedCorner: View? = null
+
+    // Privacy dots are created in ScreenDecoration's UiThread, which is not the main thread
+    private var uiExecutor: Executor? = null
+
+    private val views: Sequence<View>
+        get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl)
+
+    fun setUiExecutor(e: Executor) {
+        uiExecutor = e
+    }
+
+    @UiThread
+    fun updateRotation(rot: Int) {
+        if (rot == rotation) {
+            return
+        }
+
+        // A rotation has started, hide the views to avoid flicker
+        setCornerVisibilities(View.INVISIBLE)
+
+        if (hasMultipleHeights && (rotation % 2) != (rot % 2)) {
+            // we've changed from vertical to horizontal; update status bar height
+            needsHeightUpdate = true
+        }
+
+        rotation = rot
+        needsRotationUpdate = true
+    }
+
+    @UiThread
+    private fun updateHeights(rot: Int) {
+        val height = when (rot) {
+            0, 2 -> sbHeightPortrait
+            1, 3 -> sbHeightLandscape
+            else -> 0
+        }
+
+        views.forEach { it.layoutParams.height = height }
+    }
+
+    // Update the gravity and margins of the privacy views
+    @UiThread
+    private fun updateRotations() {
+        // To keep a view in the corner, its gravity is always the description of its current corner
+        // Therefore, just figure out which view is in which corner. This turns out to be something
+        // like (myCorner - rot) mod 4, where topLeft = 0, topRight = 1, etc. and portrait = 0, and
+        // rotating the device counter-clockwise increments rotation by 1
+
+        views.forEach { corner ->
+            val rotatedCorner = rotatedCorner(cornerForView(corner))
+            (corner.layoutParams as FrameLayout.LayoutParams).apply {
+                gravity = rotatedCorner.toGravity()
+            }
+
+            // Set the dot's view gravity to hug the status bar
+            (corner.findViewById<View>(R.id.privacy_dot)
+                    .layoutParams as FrameLayout.LayoutParams)
+                        .gravity = rotatedCorner.innerGravity()
+        }
+    }
+
+    @UiThread
+    private fun updateCornerSizes() {
+        views.forEach { corner ->
+            val rotatedCorner = rotatedCorner(cornerForView(corner))
+            val w = widthForCorner(rotatedCorner)
+            Log.d(TAG, "updateCornerSizes: setting (${cornerForView(corner)}) to $w")
+            (corner.layoutParams as FrameLayout.LayoutParams).width = w
+            corner.requestLayout()
+        }
+    }
+
+    // Designated view will be the one at statusbar's view.END
+    @UiThread
+    private fun selectDesignatedCorner(): View? {
+        if (!this::tl.isInitialized) {
+            return null
+        }
+
+        val isRtl = tl.isLayoutRtl
+
+        return when (rotation) {
+            0 -> if (isRtl) tl else tr
+            1 -> if (isRtl) tr else br
+            2 -> if (isRtl) br else bl
+            3 -> if (isRtl) bl else tl
+            else -> throw IllegalStateException("unknown rotation")
+        }
+    }
+
+    // Track the current designated corner and maybe animate to a new rotation
+    @UiThread
+    private fun updateDesignatedCorner(newCorner: View) {
+        designatedCorner = newCorner
+
+        if (animationScheduler.hasPersistentDot) {
+            designatedCorner!!.visibility = View.VISIBLE
+            designatedCorner!!.alpha = 0f
+            designatedCorner!!.animate()
+                    .alpha(1.0f)
+                    .setDuration(300)
+                    .start()
+        }
+    }
+
+    @UiThread
+    private fun setCornerVisibilities(vis: Int) {
+        views.forEach { corner ->
+            corner.visibility = vis
+        }
+    }
+
+    private fun cornerForView(v: View): Int {
+        return when (v) {
+            tl -> TOP_LEFT
+            tr -> TOP_RIGHT
+            bl -> BOTTOM_LEFT
+            br -> BOTTOM_RIGHT
+            else -> throw IllegalArgumentException("not a corner view")
+        }
+    }
+
+    private fun rotatedCorner(corner: Int): Int {
+        var modded = corner - rotation
+        if (modded < 0) {
+            modded += 4
+        }
+
+        return modded
+    }
+
+    private fun widthForCorner(corner: Int): Int {
+        return when (corner) {
+            TOP_LEFT, BOTTOM_LEFT -> leftSize
+            TOP_RIGHT, BOTTOM_RIGHT -> rightSize
+            else -> throw IllegalArgumentException("Unknown corner")
+        }
+    }
+
+    fun initialize(topLeft: View, topRight: View, bottomLeft: View, bottomRight: View) {
+        if (this::tl.isInitialized && this::tr.isInitialized &&
+                this::bl.isInitialized && this::br.isInitialized) {
+            if (tl == topLeft && tr == topRight && bl == bottomLeft && br == bottomRight) {
+                return
+            }
+        }
+
+        tl = topLeft
+        tr = topRight
+        bl = bottomLeft
+        br = bottomRight
+
+        designatedCorner = selectDesignatedCorner()
+        mainExecutor.execute {
+            animationScheduler.addCallback(systemStatusAnimationCallback)
+        }
+    }
+
+    /**
+     * Set the status bar height in portrait and landscape, in pixels. If they are the same you can
+     * pass the same value twice
+     */
+    fun setStatusBarHeights(portrait: Int, landscape: Int) {
+        sbHeightPortrait = portrait
+        sbHeightLandscape = landscape
+
+        hasMultipleHeights = portrait != landscape
+    }
+
+    /**
+     * The dot view containers will fill the margin in order to position the dots correctly
+     *
+     * @param left the space between the status bar contents and the left side of the screen
+     * @param right space between the status bar contents and the right side of the screen
+     */
+    fun setStatusBarMargins(left: Int, right: Int) {
+        leftSize = left
+        rightSize = right
+
+        needsMarginUpdate = true
+
+        // Margins come after PhoneStatusBarView does a layout pass, and so will always happen
+        // after rotation changes. It is safe to execute the updates from here
+        uiExecutor?.execute {
+            doUpdates(needsRotationUpdate, needsHeightUpdate, needsMarginUpdate)
+        }
+    }
+
+    private fun doUpdates(rot: Boolean, height: Boolean, width: Boolean) {
+        var newDesignatedCorner: View? = null
+
+        if (rot) {
+            needsRotationUpdate = false
+            updateRotations()
+            newDesignatedCorner = selectDesignatedCorner()
+        }
+
+        if (height) {
+            needsHeightUpdate = false
+            updateHeights(rotation)
+        }
+
+        if (width) {
+            needsMarginUpdate = false
+            updateCornerSizes()
+        }
+
+        if (newDesignatedCorner != null && newDesignatedCorner != designatedCorner) {
+            updateDesignatedCorner(newDesignatedCorner)
+        }
+    }
+
+    private val systemStatusAnimationCallback: SystemStatusAnimationCallback =
+            object : SystemStatusAnimationCallback {
+        override fun onSystemStatusAnimationTransitionToPersistentDot(): Animator? {
+            if (designatedCorner == null) {
+                return null
+            }
+
+            val alpha = ObjectAnimator.ofFloat(
+                    designatedCorner, "alpha", 0f, 1f)
+            alpha.duration = DURATION
+            alpha.interpolator = Interpolators.ALPHA_OUT
+            alpha.addListener(object : AnimatorListenerAdapter() {
+                override fun onAnimationStart(animator: Animator) {
+                    uiExecutor?.execute { designatedCorner?.visibility = View.VISIBLE }
+                }
+            })
+            return alpha
+        }
+
+        override fun onHidePersistentDot(): Animator? {
+            if (designatedCorner == null) {
+                return null
+            }
+
+            val alpha = ObjectAnimator.ofFloat(
+                    designatedCorner, "alpha", 1f, 0f)
+            alpha.duration = DURATION
+            alpha.interpolator = Interpolators.ALPHA_OUT
+            alpha.addListener(object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animator: Animator) {
+                    uiExecutor?.execute { designatedCorner?.visibility = View.INVISIBLE }
+                }
+            })
+            alpha.start()
+            return null
+        }
+    }
+}
+
+const val TOP_LEFT = 0
+const val TOP_RIGHT = 1
+const val BOTTOM_RIGHT = 2
+const val BOTTOM_LEFT = 3
+private const val DURATION = 160L
+private const val TAG = "PrivacyDotViewController"
+
+private fun Int.toGravity(): Int {
+    return when (this) {
+        TOP_LEFT -> Gravity.TOP or Gravity.LEFT
+        TOP_RIGHT -> Gravity.TOP or Gravity.RIGHT
+        BOTTOM_LEFT -> Gravity.BOTTOM or Gravity.LEFT
+        BOTTOM_RIGHT -> Gravity.BOTTOM or Gravity.RIGHT
+        else -> throw IllegalArgumentException("Not a corner")
+    }
+}
+
+private fun Int.innerGravity(): Int {
+    return when (this) {
+        TOP_LEFT -> Gravity.CENTER_VERTICAL or Gravity.RIGHT
+        TOP_RIGHT -> Gravity.CENTER_VERTICAL or Gravity.LEFT
+        BOTTOM_LEFT -> Gravity.CENTER_VERTICAL or Gravity.RIGHT
+        BOTTOM_RIGHT -> Gravity.CENTER_VERTICAL or Gravity.LEFT
+        else -> throw IllegalArgumentException("Not a corner")
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
new file mode 100644
index 0000000..6380dc0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.ImageView
+import com.android.settingslib.graph.ThemedBatteryDrawable
+import com.android.systemui.R
+import com.android.systemui.privacy.OngoingPrivacyChip
+import com.android.systemui.privacy.PrivacyItem
+
+interface StatusEvent {
+    val priority: Int
+    // Whether or not to force the status bar open and show a dot
+    val forceVisible: Boolean
+    val viewCreator: (context: Context) -> View
+}
+
+class BatteryEvent : StatusEvent {
+    override val priority = 50
+    override val forceVisible = false
+
+    override val viewCreator: (context: Context) -> View = { context ->
+        val iv = ImageView(context)
+        iv.setImageDrawable(ThemedBatteryDrawable(context, Color.WHITE))
+        iv.setBackgroundDrawable(ColorDrawable(Color.GREEN))
+        iv
+    }
+
+    override fun toString(): String {
+        return javaClass.simpleName
+    }
+}
+class PrivacyEvent : StatusEvent {
+    override val priority = 100
+    override val forceVisible = true
+    var privacyItems: List<PrivacyItem> = listOf()
+
+    override val viewCreator: (context: Context) -> View = { context ->
+        val v = LayoutInflater.from(context)
+                .inflate(R.layout.ongoing_privacy_chip, null) as OngoingPrivacyChip
+        v.privacyList = privacyItems
+        v
+    }
+
+    override fun toString(): String {
+        return javaClass.simpleName
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
new file mode 100644
index 0000000..6209630
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events
+
+import android.animation.ValueAnimator
+import android.content.Context
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.widget.FrameLayout
+
+import com.android.systemui.R
+import com.android.systemui.statusbar.SuperStatusBarViewFactory
+import com.android.systemui.statusbar.phone.StatusBarWindowController
+import com.android.systemui.statusbar.phone.StatusBarWindowView
+
+import javax.inject.Inject
+
+/**
+ * //TODO: this _probably_ doesn't control a window anymore
+ * Controls the window for system event animations.
+ */
+class SystemEventChipAnimationController @Inject constructor(
+    private val context: Context,
+    private val statusBarViewFactory: SuperStatusBarViewFactory,
+    private val statusBarWindowController: StatusBarWindowController
+) : SystemStatusChipAnimationCallback {
+    var showPersistentDot = false
+        set(value) {
+            field = value
+            statusBarWindowController.setForceStatusBarVisible(value)
+            maybeUpdateShowDot()
+        }
+
+    private lateinit var animationWindowView: FrameLayout
+    private lateinit var animationDotView: View
+    private lateinit var statusBarWindowView: StatusBarWindowView
+    private var currentAnimatedView: View? = null
+
+    // TODO: move to dagger
+    private var initialized = false
+
+    override fun onChipAnimationStart(
+        viewCreator: (context: Context) -> View,
+        @SystemAnimationState state: Int
+    ) {
+        if (!initialized) init()
+
+        if (state == ANIMATING_IN) {
+            currentAnimatedView = viewCreator(context)
+            animationWindowView.addView(currentAnimatedView, layoutParamsDefault)
+
+            // We are animating IN; chip comes in from View.END
+            currentAnimatedView?.apply {
+                translationX = width.toFloat()
+                alpha = 0f
+                visibility = View.VISIBLE
+            }
+        } else {
+            // We are animating away
+            currentAnimatedView?.apply {
+                translationX = 0f
+                alpha = 1f
+            }
+        }
+    }
+
+    override fun onChipAnimationEnd(@SystemAnimationState state: Int) {
+        if (state == ANIMATING_IN) {
+            // Finished animating in
+            currentAnimatedView?.apply {
+                translationX = 0f
+                alpha = 1f
+            }
+        } else {
+            // Finished animating away
+            currentAnimatedView?.apply {
+                visibility = View.INVISIBLE
+            }
+            animationWindowView.removeView(currentAnimatedView)
+        }
+    }
+
+    override fun onChipAnimationUpdate(
+        animator: ValueAnimator,
+        @SystemAnimationState state: Int
+    ) {
+        // Alpha is parameterized 0,1, and translation from (width, 0)
+        currentAnimatedView?.apply {
+            val amt = animator.animatedValue as Float
+
+            alpha = amt
+
+            val w = width
+            val translation = (1 - amt) * w
+            translationX = translation
+        }
+    }
+
+    private fun maybeUpdateShowDot() {
+        if (!initialized) return
+        if (!showPersistentDot && currentAnimatedView == null) {
+            animationDotView.visibility = View.INVISIBLE
+        }
+    }
+
+    private fun init() {
+        initialized = true
+        statusBarWindowView = statusBarViewFactory.statusBarWindowView
+        animationWindowView = LayoutInflater.from(context)
+                .inflate(R.layout.system_event_animation_window, null) as FrameLayout
+        animationDotView = animationWindowView.findViewById(R.id.dot_view)
+        val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+        lp.gravity = Gravity.END or Gravity.CENTER_VERTICAL
+        statusBarWindowView.addView(animationWindowView, lp)
+    }
+
+    private val layoutParamsDefault = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).also {
+        it.gravity = Gravity.END or Gravity.CENTER_VERTICAL
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
new file mode 100644
index 0000000..b481823
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.privacy.PrivacyItem
+import com.android.systemui.privacy.PrivacyItemController
+import com.android.systemui.statusbar.policy.BatteryController
+import javax.inject.Inject
+
+/**
+ * Listens for system events (battery, privacy, connectivity) and allows listeners
+ * to show status bar animations when they happen
+ */
+@SysUISingleton
+class SystemEventCoordinator @Inject constructor(
+    private val batteryController: BatteryController,
+    private val privacyController: PrivacyItemController
+) {
+    private lateinit var scheduler: SystemStatusAnimationScheduler
+
+    fun startObserving() {
+        /* currently unused
+        batteryController.addCallback(batteryStateListener)
+        */
+        privacyController.addCallback(privacyStateListener)
+    }
+
+    fun stopObserving() {
+        /* currently unused
+        batteryController.removeCallback(batteryStateListener)
+        */
+        privacyController.removeCallback(privacyStateListener)
+    }
+
+    fun attachScheduler(s: SystemStatusAnimationScheduler) {
+        this.scheduler = s
+    }
+
+    fun notifyPluggedIn() {
+        scheduler.onStatusEvent(BatteryEvent())
+    }
+
+    fun notifyPrivacyItemsEmpty() {
+        scheduler.setShouldShowPersistentPrivacyIndicator(false)
+    }
+
+    fun notifyPrivacyItemsChanged() {
+        val event = PrivacyEvent()
+        event.privacyItems = privacyStateListener.currentPrivacyItems
+        scheduler.onStatusEvent(event)
+    }
+
+    private val batteryStateListener = object : BatteryController.BatteryStateChangeCallback {
+        var plugged = false
+        var stateKnown = false
+        override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
+            if (!stateKnown) {
+                stateKnown = true
+                plugged = pluggedIn
+                notifyListeners()
+                return
+            }
+
+            if (plugged != pluggedIn) {
+                plugged = pluggedIn
+                notifyListeners()
+            }
+        }
+
+        private fun notifyListeners() {
+            // We only care about the plugged in status
+            if (plugged) notifyPluggedIn()
+        }
+    }
+
+    private val privacyStateListener = object : PrivacyItemController.Callback {
+        var currentPrivacyItems = listOf<PrivacyItem>()
+
+        override fun onPrivacyItemsChanged(privacyItems: List<PrivacyItem>) {
+            if (privacyItems.isNotEmpty() && currentPrivacyItems.containsAll(privacyItems)) {
+                return
+            }
+            currentPrivacyItems = privacyItems
+            notifyListeners()
+        }
+
+        private fun notifyListeners() {
+            if (currentPrivacyItems.isEmpty()) {
+                notifyPrivacyItemsEmpty()
+            } else {
+                notifyPrivacyItemsChanged()
+            }
+        }
+    }
+}
+
+private const val TAG = "SystemEventCoordinator"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
new file mode 100644
index 0000000..b85d031
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.annotation.IntDef
+import android.content.Context
+import android.os.Process
+import android.util.Log
+import android.view.View
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.phone.StatusBarWindowController
+import com.android.systemui.statusbar.policy.CallbackController
+import com.android.systemui.util.Assert
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
+
+import javax.inject.Inject
+
+/**
+ * Dead-simple scheduler for system status events. Obeys the following principles (all values TBD):
+ *      - Avoiding log spam by only allowing 12 events per minute (1event/5s)
+ *      - Waits 100ms to schedule any event for debouncing/prioritization
+ *      - Simple prioritization: Privacy > Battery > connectivity (encoded in StatusEvent)
+ *      - Only schedules a single event, and throws away lowest priority events
+ *
+ * There are 4 basic stages of animation at play here:
+ *      1. System chrome animation OUT
+ *      2. Chip animation IN
+ *      3. Chip animation OUT; potentially into a dot
+ *      4. System chrome animation IN
+ *
+ * Thus we can keep all animations synchronized with two separate ValueAnimators, one for system
+ * chrome and the other for the chip. These can animate from 0,1 and listeners can parameterize
+ * their respective views based on the progress of the animator. Interpolation differences TBD
+ */
+@SysUISingleton
+class SystemStatusAnimationScheduler @Inject constructor(
+    private val coordinator: SystemEventCoordinator,
+    private val chipAnimationController: SystemEventChipAnimationController,
+    private val statusBarWindowController: StatusBarWindowController,
+    private val systemClock: SystemClock,
+    @Main private val executor: DelayableExecutor
+) : CallbackController<SystemStatusAnimationCallback> {
+
+    /** True from the time a scheduled event starts until it's animation finishes */
+    var isActive = false
+        private set
+
+    @SystemAnimationState var animationState: Int = IDLE
+        private set
+
+    /** True if the persistent privacy dot should be active */
+    var hasPersistentDot = false
+        private set
+
+    private var scheduledEvent: StatusEvent? = null
+    private var cancelExecutionRunnable: Runnable? = null
+    private val listeners = mutableSetOf<SystemStatusAnimationCallback>()
+
+    init {
+        coordinator.attachScheduler(this)
+    }
+
+    fun onStatusEvent(event: StatusEvent) {
+        // Ignore any updates until the system is up and running
+        if (isTooEarly()) {
+            return
+        }
+
+        // Don't deal with threading for now (no need let's be honest)
+        Assert.isMainThread()
+        if (event.priority > scheduledEvent?.priority ?: -1) {
+            if (DEBUG) {
+                Log.d(TAG, "scheduling event $event")
+            }
+            scheduleEvent(event)
+        } else {
+            if (DEBUG) {
+                Log.d(TAG, "ignoring event $event")
+            }
+        }
+    }
+
+    private fun clearDotIfVisible() {
+        notifyHidePersistentDot()
+    }
+
+    fun setShouldShowPersistentPrivacyIndicator(should: Boolean) {
+        if (hasPersistentDot == should) {
+            return
+        }
+
+        hasPersistentDot = should
+
+        if (!hasPersistentDot) {
+            clearDotIfVisible()
+        }
+    }
+
+    private fun isTooEarly(): Boolean {
+        Log.d(TAG, "time=> ${systemClock.uptimeMillis() - Process.getStartUptimeMillis()}")
+        return systemClock.uptimeMillis() - Process.getStartUptimeMillis() < MIN_UPTIME
+    }
+
+    /**
+     * Clear the scheduled event (if any) and schedule a new one
+     */
+    private fun scheduleEvent(event: StatusEvent) {
+        scheduledEvent = event
+        if (scheduledEvent!!.forceVisible) {
+            hasPersistentDot = true
+        }
+
+        // Schedule the animation to start after a debounce period
+        cancelExecutionRunnable = executor.executeDelayed({
+            cancelExecutionRunnable = null
+            animationState = ANIMATING_IN
+            statusBarWindowController.setForceStatusBarVisible(true)
+
+            val entranceAnimator = ValueAnimator.ofFloat(1f, 0f)
+            entranceAnimator.duration = ENTRANCE_ANIM_LENGTH
+            entranceAnimator.addListener(systemAnimatorAdapter)
+            entranceAnimator.addUpdateListener(systemUpdateListener)
+
+            val chipAnimator = ValueAnimator.ofFloat(0f, 1f)
+            chipAnimator.duration = CHIP_ANIM_LENGTH
+            chipAnimator.addListener(
+                    ChipAnimatorAdapter(RUNNING_CHIP_ANIM, scheduledEvent!!.viewCreator))
+            chipAnimator.addUpdateListener(chipUpdateListener)
+
+            val aSet2 = AnimatorSet()
+            aSet2.playSequentially(entranceAnimator, chipAnimator)
+            aSet2.start()
+
+            executor.executeDelayed({
+                animationState = ANIMATING_OUT
+
+                val systemAnimator = ValueAnimator.ofFloat(0f, 1f)
+                systemAnimator.duration = ENTRANCE_ANIM_LENGTH
+                systemAnimator.addListener(systemAnimatorAdapter)
+                systemAnimator.addUpdateListener(systemUpdateListener)
+
+                val chipAnimator = ValueAnimator.ofFloat(1f, 0f)
+                chipAnimator.duration = CHIP_ANIM_LENGTH
+                chipAnimator.addListener(ChipAnimatorAdapter(IDLE, scheduledEvent!!.viewCreator))
+                chipAnimator.addUpdateListener(chipUpdateListener)
+
+                val aSet2 = AnimatorSet()
+
+                aSet2.play(chipAnimator).before(systemAnimator)
+                if (hasPersistentDot) {
+                    val dotAnim = notifyTransitionToPersistentDot()
+                    if (dotAnim != null) aSet2.playTogether(systemAnimator, dotAnim)
+                }
+
+                aSet2.start()
+
+                statusBarWindowController.setForceStatusBarVisible(false)
+                scheduledEvent = null
+            }, DISPLAY_LENGTH)
+        }, DELAY)
+    }
+
+    private fun notifyTransitionToPersistentDot(): Animator? {
+        val anims: List<Animator> = listeners.mapNotNull {
+            it.onSystemStatusAnimationTransitionToPersistentDot()
+        }
+        if (anims.isNotEmpty()) {
+            val aSet = AnimatorSet()
+            aSet.playTogether(anims)
+            return aSet
+        }
+
+        return null
+    }
+
+    private fun notifyHidePersistentDot(): Animator? {
+        val anims: List<Animator> = listeners.mapNotNull {
+            it.onHidePersistentDot()
+        }
+
+        if (anims.isNotEmpty()) {
+            val aSet = AnimatorSet()
+            aSet.playTogether(anims)
+            return aSet
+        }
+
+        return null
+    }
+
+    private fun notifySystemStart() {
+        listeners.forEach { it.onSystemChromeAnimationStart() }
+    }
+
+    private fun notifySystemFinish() {
+        listeners.forEach { it.onSystemChromeAnimationEnd() }
+    }
+
+    private fun notifySystemAnimationUpdate(anim: ValueAnimator) {
+        listeners.forEach { it.onSystemChromeAnimationUpdate(anim) }
+    }
+
+    override fun addCallback(listener: SystemStatusAnimationCallback) {
+        Assert.isMainThread()
+
+        if (listeners.isEmpty()) {
+            coordinator.startObserving()
+        }
+        listeners.add(listener)
+    }
+
+    override fun removeCallback(listener: SystemStatusAnimationCallback) {
+        Assert.isMainThread()
+
+        listeners.remove(listener)
+        if (listeners.isEmpty()) {
+            coordinator.stopObserving()
+        }
+    }
+
+    private val systemUpdateListener = ValueAnimator.AnimatorUpdateListener {
+        anim -> notifySystemAnimationUpdate(anim)
+    }
+
+    private val systemAnimatorAdapter = object : AnimatorListenerAdapter() {
+        override fun onAnimationEnd(p0: Animator?) {
+            notifySystemFinish()
+        }
+
+        override fun onAnimationStart(p0: Animator?) {
+            notifySystemStart()
+        }
+    }
+
+    private val chipUpdateListener = ValueAnimator.AnimatorUpdateListener {
+        anim -> chipAnimationController.onChipAnimationUpdate(anim, animationState)
+    }
+
+    inner class ChipAnimatorAdapter(
+        @SystemAnimationState val endState: Int,
+        val viewCreator: (context: Context) -> View
+    ) : AnimatorListenerAdapter() {
+        override fun onAnimationEnd(p0: Animator?) {
+            chipAnimationController.onChipAnimationEnd(animationState)
+            animationState = endState
+        }
+
+        override fun onAnimationStart(p0: Animator?) {
+            chipAnimationController.onChipAnimationStart(viewCreator, animationState)
+        }
+    }
+}
+
+/**
+ * The general idea here is that this scheduler will run two value animators, and provide
+ * animator-like callbacks for each kind of animation. The SystemChrome animation is expected to
+ * create space for the chip animation to display. This means hiding the system elements in the
+ * status bar and keyguard.
+ *
+ * TODO: the chip animation really only has one client, we can probably remove it from this
+ * interface
+ *
+ * The value animators themselves are simple animators from 0.0 to 1.0. Listeners can apply any
+ * interpolation they choose but realistically these are most likely to be simple alpha transitions
+ */
+interface SystemStatusAnimationCallback {
+    @JvmDefault fun onSystemChromeAnimationUpdate(animator: ValueAnimator) {}
+    @JvmDefault fun onSystemChromeAnimationStart() {}
+    @JvmDefault fun onSystemChromeAnimationEnd() {}
+
+    // Best method name, change my mind
+    @JvmDefault fun onSystemStatusAnimationTransitionToPersistentDot(): Animator? { return null }
+    @JvmDefault fun onHidePersistentDot(): Animator? { return null }
+}
+
+interface SystemStatusChipAnimationCallback {
+    fun onChipAnimationUpdate(animator: ValueAnimator, @SystemAnimationState state: Int) {}
+
+    fun onChipAnimationStart(
+        viewCreator: (context: Context) -> View,
+        @SystemAnimationState state: Int
+    ) {}
+
+    fun onChipAnimationEnd(@SystemAnimationState state: Int) {}
+}
+
+/**
+ */
+@Retention(AnnotationRetention.SOURCE)
+@IntDef(
+        value = [
+            IDLE, ANIMATING_IN, RUNNING_CHIP_ANIM, ANIMATING_OUT
+        ]
+)
+annotation class SystemAnimationState
+
+/** No animation is in progress */
+const val IDLE = 0
+/** System is animating out, and chip is animating in */
+const val ANIMATING_IN = 1
+/** Chip has animated in and is awaiting exit animation, and optionally playing its own animation */
+const val RUNNING_CHIP_ANIM = 2
+/** Chip is animating away and system is animating back */
+const val ANIMATING_OUT = 3
+
+private const val TAG = "SystemStatusAnimationScheduler"
+private const val DELAY: Long = 100
+private const val DISPLAY_LENGTH = 5000L
+private const val ENTRANCE_ANIM_LENGTH = 500L
+private const val CHIP_ANIM_LENGTH = 500L
+private const val MIN_UPTIME: Long = 5 * 1000
+
+private const val DEBUG = false
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 8446b4e6..caf4720 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -60,7 +60,7 @@
     private NotificationShelf mShelf;
     private int mZDistanceBetweenElements;
     private int mBaseZHeight;
-    private int mMaxLayoutHeight;
+    private int mContentHeight;
     private ExpandableView mLastVisibleBackgroundChild;
     private float mCurrentScrollVelocity;
     private int mStatusBarState;
@@ -84,6 +84,75 @@
     private boolean mIsShadeOpening;
     private float mSectionPadding;
 
+    /** Distance of top of notifications panel from top of screen. */
+    private float mStackY = 0;
+
+    /** Height of notifications panel. */
+    private float mStackHeight = 0;
+
+    /** Fraction of shade expansion. */
+    private float mExpansionFraction;
+
+    /** Height of the notifications panel without top padding when expansion completes. */
+    private float mStackEndHeight;
+
+    /**
+     * @return Height of the notifications panel without top padding when expansion completes.
+     */
+    public float getStackEndHeight() {
+        return mStackEndHeight;
+    }
+
+    /**
+     * @param stackEndHeight Height of the notifications panel without top padding
+     *                       when expansion completes.
+     */
+    public void setStackEndHeight(float stackEndHeight) {
+        mStackEndHeight = stackEndHeight;
+    }
+
+    /**
+     * @param stackY Distance of top of notifications panel from top of screen.
+     */
+    public void setStackY(float stackY) {
+        mStackY = stackY;
+    }
+
+    /**
+     * @return Distance of top of notifications panel from top of screen.
+     */
+    public float getStackY() {
+        return mStackY;
+    }
+
+    /**
+     * @param expansionFraction Fraction of shade expansion.
+     */
+    public void setExpansionFraction(float expansionFraction) {
+        mExpansionFraction = expansionFraction;
+    }
+
+    /**
+     * @return Fraction of shade expansion.
+     */
+    public float getExpansionFraction() {
+        return mExpansionFraction;
+    }
+
+    /**
+     * @param stackHeight Height of notifications panel.
+     */
+    public void setStackHeight(float stackHeight) {
+        mStackHeight = stackHeight;
+    }
+
+    /**
+     * @return Height of notifications panel.
+     */
+    public float getStackHeight() {
+        return mStackHeight;
+    }
+
     /** Tracks the state from AlertingNotificationManager#hasNotifications() */
     private boolean mHasAlertEntries;
 
@@ -263,8 +332,8 @@
         if (mDozeAmount == 1.0f && !isPulseExpanding()) {
             return mShelf.getHeight();
         }
-        int height = Math.max(mLayoutMinHeight,
-                Math.min(mLayoutHeight, mMaxLayoutHeight) - mTopPadding);
+        int height = (int) Math.max(mLayoutMinHeight,
+                Math.min(mLayoutHeight, mContentHeight) - mTopPadding);
         if (ignorePulseHeight) {
             return height;
         }
@@ -313,8 +382,12 @@
         return mShelf;
     }
 
-    public void setLayoutMaxHeight(int maxLayoutHeight) {
-        mMaxLayoutHeight = maxLayoutHeight;
+    public void setContentHeight(int contentHeight) {
+        mContentHeight = contentHeight;
+    }
+
+    public float getContentHeight() {
+        return mContentHeight;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index fb72ac3..733a9f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -658,6 +658,14 @@
             y = getHeight() - getEmptyBottomMargin();
             mDebugPaint.setColor(Color.GREEN);
             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+
+            y = (int) (mAmbientState.getStackY());
+            mDebugPaint.setColor(Color.CYAN);
+            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+
+            y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight());
+            mDebugPaint.setColor(Color.BLUE);
+            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
         }
     }
 
@@ -1123,18 +1131,37 @@
                 mTopPaddingNeedsAnimation = true;
                 mNeedsAnimation = true;
             }
+            updateStackPosition();
             requestChildrenUpdate();
             notifyHeightChangeListener(null, animate);
         }
     }
 
     /**
+     * Apply expansion fraction to the y position and height of the notifications panel.
+     */
+    private void updateStackPosition() {
+        // Consider interpolating from an mExpansionStartY for use on lockscreen and AOD
+        mAmbientState.setStackY(
+                MathUtils.lerp(0, mTopPadding, mAmbientState.getExpansionFraction()));
+        final float shadeBottom = getHeight() - getEmptyBottomMargin();
+        mAmbientState.setStackEndHeight(shadeBottom - mTopPadding);
+        mAmbientState.setStackHeight(
+                MathUtils.lerp(0, shadeBottom - mTopPadding, mAmbientState.getExpansionFraction()));
+    }
+
+    /**
      * Update the height of the panel.
      *
      * @param height the expanded height of the panel
      */
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public void setExpandedHeight(float height) {
+        final float shadeBottom = getHeight() - getEmptyBottomMargin();
+        final float expansionFraction = MathUtils.constrain(height / shadeBottom, 0f, 1f);
+        mAmbientState.setExpansionFraction(expansionFraction);
+        updateStackPosition();
+
         mExpandedHeight = height;
         setIsExpanded(height > 0);
         int minExpansionHeight = getMinExpansionHeight();
@@ -2067,7 +2094,8 @@
         mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomMargin;
         updateScrollability();
         clampScrollPosition();
-        mAmbientState.setLayoutMaxHeight(mContentHeight);
+        updateStackPosition();
+        mAmbientState.setContentHeight(mContentHeight);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 7776e69..4fc49ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -788,6 +788,10 @@
         return mView.getTranslationX();
     }
 
+    public int indexOfChild(View view) {
+        return mView.indexOfChild(view);
+    }
+
     public void setOnHeightChangedListener(
             ExpandableView.OnHeightChangedListener listener) {
         mView.setOnHeightChangedListener(listener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index bbdbe80..3e1a781 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -20,21 +20,22 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Resources;
-import android.util.Log;
 import android.util.MathUtils;
 import android.view.View;
 import android.view.ViewGroup;
 
 import com.android.systemui.R;
-import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.notification.dagger.SilentHeader;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.FooterView;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * The Algorithm of the {@link com.android.systemui.statusbar.notification.stack
@@ -92,20 +93,16 @@
 
         // First we reset the view states to their default values.
         resetChildViewStates();
-
         initAlgorithmState(mHostView, algorithmState, ambientState);
-
         updatePositionsForState(algorithmState, ambientState);
-
         updateZValuesForState(algorithmState, ambientState);
-
         updateHeadsUpStates(algorithmState, ambientState);
         updatePulsingStates(algorithmState, ambientState);
 
         updateDimmedActivatedHideSensitive(ambientState, algorithmState);
         updateClipping(algorithmState, ambientState);
         updateSpeedBumpState(algorithmState, speedBumpIndex);
-        updateShelfState(ambientState);
+        updateShelfState(algorithmState, ambientState);
         getNotificationChildrenStates(algorithmState, ambientState);
     }
 
@@ -144,10 +141,13 @@
 
     }
 
-    private void updateShelfState(AmbientState ambientState) {
+    private void updateShelfState(
+            StackScrollAlgorithmState algorithmState,
+            AmbientState ambientState) {
+
         NotificationShelf shelf = ambientState.getShelf();
         if (shelf != null) {
-            shelf.updateState(ambientState);
+            shelf.updateState(algorithmState, ambientState);
         }
     }
 
@@ -172,7 +172,8 @@
                     && ((ExpandableNotificationRow) child).isPinned();
             if (mClipNotificationScrollToTop
                     && (!state.inShelf || (isHeadsUp && !firstHeadsUp))
-                    && newYTranslation < clipStart) {
+                    && newYTranslation < clipStart
+                    && !ambientState.isShadeOpening()) {
                 // The previous view is overlapping on top, clip!
                 float overlapAmount = clipStart - newYTranslation;
                 state.clipTopAmount = (int) overlapAmount;
@@ -217,7 +218,6 @@
     private void initAlgorithmState(ViewGroup hostView, StackScrollAlgorithmState state,
             AmbientState ambientState) {
         float bottomOverScroll = ambientState.getOverScrollAmount(false /* onTop */);
-
         int scrollY = ambientState.getScrollY();
 
         // Due to the overScroller, the stackscroller can have negative scroll state. This is
@@ -230,7 +230,6 @@
         state.visibleChildren.clear();
         state.visibleChildren.ensureCapacity(childCount);
         int notGoneIndex = 0;
-        ExpandableView lastView = null;
         for (int i = 0; i < childCount; i++) {
             ExpandableView v = (ExpandableView) hostView.getChildAt(i);
             if (v.getVisibility() != View.GONE) {
@@ -255,12 +254,101 @@
                 }
             }
         }
-        ExpandableNotificationRow expandingNotification = ambientState.getExpandingNotification();
-        state.indexOfExpandingNotification = expandingNotification != null
-                ? expandingNotification.isChildInGroup()
-                ? state.visibleChildren.indexOf(expandingNotification.getNotificationParent())
-                : state.visibleChildren.indexOf(expandingNotification)
-                : -1;
+
+        state.firstViewInShelf = null;
+        // Save y, sectionStart, sectionEnd from when shade is fully expanded.
+        // Consider updating these states in updateContentView instead so that we don't have to
+        // recalculate in every frame.
+        float currentY = -scrollY;
+        int sectionStartIndex = 0;
+        int sectionEndIndex = 0;
+        for (int i = 0; i < state.visibleChildren.size(); i++) {
+            final ExpandableView view = state.visibleChildren.get(i);
+            // Add space between sections.
+            final boolean applyGapHeight = childNeedsGapHeight(
+                    ambientState.getSectionProvider(), i,
+                    view, getPreviousView(i, state));
+            if (applyGapHeight) {
+                currentY += mGapHeight;
+            }
+
+            // Save index of first view in the shelf
+            final float shelfStart = ambientState.getStackEndHeight()
+                    - ambientState.getShelf().getIntrinsicHeight();
+            if (currentY >= shelfStart
+                    && !(view instanceof FooterView)
+                    && state.firstViewInShelf == null) {
+                state.firstViewInShelf = view;
+            }
+
+            // Record y position when fully expanded
+            ExpansionData expansionData = new ExpansionData();
+            expansionData.fullyExpandedY = currentY;
+            state.expansionData.put(view, expansionData);
+
+            if (ambientState.getSectionProvider()
+                    .beginsSection(view, getPreviousView(i, state))) {
+
+                // Save section start/end for views in the section before this new section
+                ExpandableView sectionStartView = state.visibleChildren.get(sectionStartIndex);
+                final float sectionStart =
+                        state.expansionData.get(sectionStartView).fullyExpandedY;
+
+                ExpandableView sectionEndView = state.visibleChildren.get(sectionEndIndex);
+                float sectionEnd = state.expansionData.get(sectionEndView).fullyExpandedY
+                        + sectionEndView.getIntrinsicHeight();
+
+                // If we show the shelf, trim section end to shelf start
+                // This means section end > start for views in the shelf
+                if (state.firstViewInShelf != null && sectionEnd > shelfStart) {
+                    sectionEnd = shelfStart;
+                }
+
+                // Update section bounds of every view in the previous section
+                // Consider using shared SectionInfo for views in same section to avoid looping back
+                for (int j = sectionStartIndex; j < i; j++) {
+                    ExpandableView sectionView = state.visibleChildren.get(j);
+                    ExpansionData viewExpansionData =
+                            state.expansionData.get(sectionView);
+                    viewExpansionData.sectionStart = sectionStart;
+                    viewExpansionData.sectionEnd = sectionEnd;
+                    state.expansionData.put(sectionView, viewExpansionData);
+                }
+                sectionStartIndex = i;
+
+                if (view instanceof FooterView) {
+                    // Also record section bounds for FooterView (same as its own)
+                    // because it is the last view and we won't get to this point again
+                    // after the loop ends
+                    ExpansionData footerExpansionData = state.expansionData.get(view);
+                    footerExpansionData.sectionStart = expansionData.fullyExpandedY;
+                    footerExpansionData.sectionEnd = expansionData.fullyExpandedY
+                            + view.getIntrinsicHeight();
+                    state.expansionData.put(view, footerExpansionData);
+                }
+            }
+            sectionEndIndex = i;
+            currentY = currentY
+                    + getMaxAllowedChildHeight(view)
+                    + mPaddingBetweenElements;
+        }
+
+        // Which view starts the section of the view right before the shelf?
+        // Save it for later when we clip views in that section to shelf start.
+        state.firstViewInOverflowSection = null;
+        if (state.firstViewInShelf != null) {
+            ExpandableView nextView = null;
+            final int startIndex = state.visibleChildren.indexOf(state.firstViewInShelf);
+            for (int i = startIndex - 1; i >= 0; i--) {
+                ExpandableView view = state.visibleChildren.get(i);
+                if (nextView != null && ambientState.getSectionProvider()
+                        .beginsSection(nextView, view)) {
+                    break;
+                }
+                nextView = view;
+            }
+            state.firstViewInOverflowSection = nextView;
+        }
     }
 
     private int updateNotGoneIndex(StackScrollAlgorithmState state, int notGoneIndex,
@@ -272,6 +360,10 @@
         return notGoneIndex;
     }
 
+    private ExpandableView getPreviousView(int i, StackScrollAlgorithmState algorithmState) {
+        return i > 0 ? algorithmState.visibleChildren.get(i - 1) : null;
+    }
+
     /**
      * Determine the positions for the views. This is the main part of the algorithm.
      *
@@ -288,6 +380,15 @@
         }
     }
 
+    private void setLocation(ExpandableViewState expandableViewState, float currentYPosition,
+            int i) {
+        expandableViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
+        if (currentYPosition <= 0) {
+            expandableViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
+        }
+    }
+
+    // TODO(b/172289889) polish shade open from HUN
     /**
      * Populates the {@link ExpandableViewState} for a single child.
      *
@@ -306,53 +407,84 @@
             StackScrollAlgorithmState algorithmState,
             AmbientState ambientState,
             float currentYPosition) {
-        ExpandableView child = algorithmState.visibleChildren.get(i);
-        ExpandableView previousChild = i > 0 ? algorithmState.visibleChildren.get(i - 1) : null;
+
+        ExpandableView view = algorithmState.visibleChildren.get(i);
+        ExpandableViewState viewState = view.getViewState();
+        viewState.location = ExpandableViewState.LOCATION_UNKNOWN;
+        viewState.alpha = 1f - ambientState.getHideAmount();
+
+        if (view.mustStayOnScreen() && viewState.yTranslation >= 0) {
+            // Even if we're not scrolled away we're in view and we're also not in the
+            // shelf. We can relax the constraints and let us scroll off the top!
+            float end = viewState.yTranslation + viewState.height + ambientState.getStackY();
+            viewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation();
+        }
+
+        // TODO(b/172289889) move sectionFraction and showSection to initAlgorithmState
+        // Get fraction of section showing, and later apply it to view height and gaps between views
+        float sectionFraction = 1f;
+        boolean showSection = true;
+
+        if (!ambientState.isOnKeyguard()
+                && !ambientState.isPulseExpanding()
+                && ambientState.isExpansionChanging()) {
+
+            final ExpansionData expansionData = algorithmState.expansionData.get(view);
+            final float sectionHeight = expansionData.sectionEnd - expansionData.sectionStart;
+            sectionFraction = MathUtils.constrain(
+                    (ambientState.getStackHeight() - expansionData.sectionStart) / sectionHeight,
+                    0f, 1f);
+            showSection = expansionData.sectionStart < ambientState.getStackHeight();
+        }
+
+        // Add gap between sections.
         final boolean applyGapHeight =
                 childNeedsGapHeight(
                         ambientState.getSectionProvider(), i,
-                        child, previousChild);
-        ExpandableViewState childViewState = child.getViewState();
-        childViewState.location = ExpandableViewState.LOCATION_UNKNOWN;
-
+                        view, getPreviousView(i, algorithmState));
         if (applyGapHeight) {
-            currentYPosition += mGapHeight;
-        }
-        int childHeight = getMaxAllowedChildHeight(child);
-        childViewState.yTranslation = currentYPosition;
-        childViewState.alpha = 1f - ambientState.getHideAmount();
-
-        boolean isFooterView = child instanceof FooterView;
-        boolean isEmptyShadeView = child instanceof EmptyShadeView;
-
-        childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
-        float inset = ambientState.getTopPadding() + ambientState.getStackTranslation()
-                + ambientState.getSectionPadding();
-        if (child.mustStayOnScreen() && childViewState.yTranslation >= 0) {
-            // Even if we're not scrolled away we're in view and we're also not in the
-            // shelf. We can relax the constraints and let us scroll off the top!
-            float end = childViewState.yTranslation + childViewState.height + inset;
-            childViewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation();
-        }
-        if (isFooterView) {
-            childViewState.yTranslation = Math.min(childViewState.yTranslation,
-                    ambientState.getInnerHeight() - childHeight);
-        } else if (isEmptyShadeView) {
-            childViewState.yTranslation = ambientState.getInnerHeight() - childHeight
-                    + ambientState.getStackTranslation() * 0.25f;
-        } else if (child != ambientState.getTrackedHeadsUpRow()) {
-            clampPositionToShelf(child, childViewState, ambientState);
+            currentYPosition += sectionFraction * mGapHeight;
         }
 
-        currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements;
-        if (currentYPosition <= 0) {
-            childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
-        }
-        if (childViewState.location == ExpandableViewState.LOCATION_UNKNOWN) {
-            Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
+        viewState.yTranslation = currentYPosition;
+
+        if (view instanceof SectionHeaderView) {
+            // Add padding before sections for overscroll effect.
+            viewState.yTranslation += ambientState.getSectionPadding();
         }
 
-        childViewState.yTranslation += inset;
+        if (view != ambientState.getTrackedHeadsUpRow()) {
+            if (ambientState.isExpansionChanging()) {
+                viewState.hidden = !showSection;
+                viewState.inShelf = algorithmState.firstViewInShelf != null
+                        && i >= algorithmState.visibleChildren.indexOf(
+                                algorithmState.firstViewInShelf)
+                        && !(view instanceof FooterView);
+            } else {
+                // When pulsing (incoming notification on AOD), innerHeight is 0; clamp all
+                // to shelf start, thereby hiding all notifications (except the first one, which we
+                // later unhide in updatePulsingState)
+                final int shelfStart = ambientState.getInnerHeight()
+                        - ambientState.getShelf().getIntrinsicHeight();
+                if (!(view instanceof FooterView)) {
+                    viewState.yTranslation = Math.min(viewState.yTranslation, shelfStart);
+                }
+                if (viewState.yTranslation >= shelfStart) {
+                    viewState.hidden = !view.isExpandAnimationRunning()
+                            && !view.hasExpandingChild()
+                            && !(view instanceof FooterView);
+                    viewState.inShelf = true;
+                    // Notifications in the shelf cannot be visible HUNs.
+                    viewState.headsUpIsVisible = false;
+                }
+            }
+            viewState.height = (int) MathUtils.lerp(
+                    0, getMaxAllowedChildHeight(view), sectionFraction);
+        }
+
+        currentYPosition += viewState.height + sectionFraction * mPaddingBetweenElements;
+        setLocation(view.getViewState(), currentYPosition, i);
+        viewState.yTranslation += ambientState.getStackY();
         return currentYPosition;
     }
 
@@ -393,10 +525,10 @@
             int visibleIndex,
             View child,
             View previousChild) {
-
-        boolean needsGapHeight = sectionProvider.beginsSection(child, previousChild)
-                && visibleIndex > 0;
-        return needsGapHeight;
+        return sectionProvider.beginsSection(child, previousChild)
+                && visibleIndex > 0
+                && !(previousChild instanceof SilentHeader)
+                && !(child instanceof FooterView);
     }
 
     private void updatePulsingStates(StackScrollAlgorithmState algorithmState,
@@ -514,42 +646,6 @@
         childState.yTranslation = newTranslation;
     }
 
-    /**
-     * Clamp the height of the child down such that its end is at most on the beginning of
-     * the shelf.
-     *
-     * @param childViewState the view state of the child
-     * @param ambientState   the ambient state
-     */
-    private void clampPositionToShelf(ExpandableView child,
-            ExpandableViewState childViewState,
-            AmbientState ambientState) {
-        if (ambientState.getShelf() == null) {
-            return;
-        }
-
-        ExpandableNotificationRow trackedHeadsUpRow = ambientState.getTrackedHeadsUpRow();
-        boolean isBeforeTrackedHeadsUp = trackedHeadsUpRow != null
-                && mHostView.indexOfChild(child) < mHostView.indexOfChild(trackedHeadsUpRow);
-
-        int shelfStart = ambientState.getInnerHeight()
-                - ambientState.getShelf().getIntrinsicHeight();
-        if (ambientState.isAppearing() && !child.isAboveShelf() && !isBeforeTrackedHeadsUp) {
-            // Don't show none heads-up notifications while in appearing phase.
-            childViewState.yTranslation = Math.max(childViewState.yTranslation, shelfStart);
-        }
-        childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart);
-        if (child instanceof SectionHeaderView) {
-            // Add padding before sections for overscroll effect.
-            childViewState.yTranslation += ambientState.getSectionPadding();
-        }
-        if (childViewState.yTranslation >= shelfStart) {
-            childViewState.hidden = !child.isExpandAnimationRunning() && !child.hasExpandingChild();
-            childViewState.inShelf = true;
-            childViewState.headsUpIsVisible = false;
-        }
-    }
-
     protected int getMaxAllowedChildHeight(View child) {
         if (child instanceof ExpandableView) {
             ExpandableView expandableView = (ExpandableView) child;
@@ -641,6 +737,35 @@
         this.mIsExpanded = isExpanded;
     }
 
+    /**
+     * Data used to layout views while shade expansion changes.
+     */
+    public class ExpansionData {
+
+        /**
+         * Y position of top of first view in section.
+         */
+        public float sectionStart;
+
+        /**
+         * Y position of bottom of last view in section.
+         */
+        public float sectionEnd;
+
+        /**
+         * Y position of view when shade is fully expanded.
+         * Does not include distance between top notifications panel and top of screen.
+         */
+        public float fullyExpandedY;
+
+        /**
+         * Whether this notification is in the same section as the notification right before the
+         * shelf. Used to determine which notification should be clipped to shelf start while
+         * shade expansion changes.
+         */
+        public boolean inOverflowingSection;
+    }
+
     public class StackScrollAlgorithmState {
 
         /**
@@ -649,15 +774,26 @@
         public int scrollY;
 
         /**
+         * First view in shelf.
+         */
+        public ExpandableView firstViewInShelf;
+
+        /**
+         * First view in section overflowing into shelf while shade expansion changes.
+         */
+        public ExpandableView firstViewInOverflowSection;
+
+        /**
+         * Map of view to ExpansionData used for layout during shade expansion.
+         * Use view instead of index as key, because visibleChildren indices do not match the ones
+         * used in the shelf.
+         */
+        public Map<ExpandableView, ExpansionData> expansionData = new HashMap<>();
+
+        /**
          * The children from the host view which are not gone.
          */
-        public final ArrayList<ExpandableView> visibleChildren = new ArrayList<ExpandableView>();
-
-        private int indexOfExpandingNotification;
-
-        public int getIndexOfExpandingNotification() {
-            return indexOfExpandingNotification;
-        }
+        public final ArrayList<ExpandableView> visibleChildren = new ArrayList<>();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 40e6b40..4e57e44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -19,10 +19,16 @@
 import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS;
 import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO;
 
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN;
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT;
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
+
+import android.animation.ValueAnimator;
 import android.annotation.Nullable;
 import android.app.Fragment;
 import android.os.Bundle;
 import android.os.Parcelable;
+import android.util.Log;
 import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -36,6 +42,9 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
@@ -44,6 +53,8 @@
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
 
+import org.jetbrains.annotations.NotNull;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -55,7 +66,8 @@
  * updated by the StatusBarIconController and DarkIconManager while it is attached.
  */
 public class CollapsedStatusBarFragment extends Fragment implements CommandQueue.Callbacks,
-        StatusBarStateController.StateListener {
+        StatusBarStateController.StateListener,
+        SystemStatusAnimationCallback {
 
     public static final String TAG = "CollapsedStatusBarFragment";
     private static final String EXTRA_PANEL_STATE = "panel_state";
@@ -78,6 +90,8 @@
     private View mOperatorNameFrame;
     private CommandQueue mCommandQueue;
     private OngoingCallController mOngoingCallController;
+    private final SystemStatusAnimationScheduler mAnimationScheduler;
+    private final PrivacyDotViewController mDotViewController;
 
     private List<String> mBlockedIcons = new ArrayList<>();
 
@@ -103,8 +117,14 @@
     };
 
     @Inject
-    public CollapsedStatusBarFragment(OngoingCallController ongoingCallController) {
+    public CollapsedStatusBarFragment(
+            OngoingCallController ongoingCallController,
+            SystemStatusAnimationScheduler animationScheduler,
+            PrivacyDotViewController dotViewController
+    ) {
         mOngoingCallController = ongoingCallController;
+        mAnimationScheduler = animationScheduler;
+        mDotViewController = dotViewController;
     }
 
     @Override
@@ -127,6 +147,9 @@
     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         mStatusBar = (PhoneStatusBarView) view;
+        View contents = mStatusBar.findViewById(R.id.status_bar_contents);
+        contents.addOnLayoutChangeListener(mStatusBarLayoutListener);
+        updateStatusBarLocation(contents.getLeft(), contents.getRight());
         if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_PANEL_STATE)) {
             mStatusBar.restoreHierarchyState(
                     savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));
@@ -143,6 +166,7 @@
         showClock(false);
         initEmergencyCryptkeeperText();
         initOperatorName();
+        mAnimationScheduler.addCallback(this);
     }
 
     @Override
@@ -208,6 +232,7 @@
         if (displayId != getContext().getDisplayId()) {
             return;
         }
+        Log.d(TAG, "disable: ");
         state1 = adjustDisableFlags(state1);
         final int old1 = mDisabled1;
         final int diff1 = state1 ^ old1;
@@ -292,19 +317,22 @@
         return false;
     }
 
-    public void hideSystemIconArea(boolean animate) {
+    private void hideSystemIconArea(boolean animate) {
         animateHide(mSystemIconArea, animate);
     }
 
-    public void showSystemIconArea(boolean animate) {
-        animateShow(mSystemIconArea, animate);
+    private void showSystemIconArea(boolean animate) {
+        // Only show the system icon area if we are not currently animating
+        if (mAnimationScheduler.getAnimationState() == IDLE) {
+            animateShow(mSystemIconArea, animate);
+        }
     }
 
-    public void hideClock(boolean animate) {
+    private void hideClock(boolean animate) {
         animateHiddenState(mClockView, clockHiddenMode(), animate);
     }
 
-    public void showClock(boolean animate) {
+    private void showClock(boolean animate) {
         animateShow(mClockView, animate);
     }
 
@@ -425,12 +453,60 @@
     }
 
     @Override
-    public void onStateChanged(int newState) {
-
-    }
+    public void onStateChanged(int newState) { }
 
     @Override
     public void onDozingChanged(boolean isDozing) {
         disable(getContext().getDisplayId(), mDisabled1, mDisabled1, false /* animate */);
     }
+
+    @Override
+    public void onSystemChromeAnimationStart() {
+        if (mAnimationScheduler.getAnimationState() == ANIMATING_OUT
+                && !isSystemIconAreaDisabled()) {
+            mSystemIconArea.setVisibility(View.VISIBLE);
+            mSystemIconArea.setAlpha(0f);
+        }
+    }
+
+    @Override
+    public void onSystemChromeAnimationEnd() {
+        // Make sure the system icons are out of the way
+        if (mAnimationScheduler.getAnimationState() == ANIMATING_IN) {
+            mSystemIconArea.setVisibility(View.INVISIBLE);
+            mSystemIconArea.setAlpha(0f);
+        } else {
+            if (isSystemIconAreaDisabled()) {
+                // don't unhide
+                return;
+            }
+
+            mSystemIconArea.setAlpha(1f);
+            mSystemIconArea.setVisibility(View.VISIBLE);
+        }
+    }
+
+    @Override
+    public void onSystemChromeAnimationUpdate(@NotNull ValueAnimator animator) {
+        mSystemIconArea.setAlpha((float) animator.getAnimatedValue());
+    }
+
+    private boolean isSystemIconAreaDisabled() {
+        return (mDisabled1 & DISABLE_SYSTEM_INFO) != 0 || (mDisabled2 & DISABLE2_SYSTEM_ICONS) != 0;
+    }
+
+    private void updateStatusBarLocation(int left, int right) {
+        int leftMargin = left - mStatusBar.getLeft();
+        int rightMargin = mStatusBar.getRight() - right;
+
+        mDotViewController.setStatusBarMargins(leftMargin, rightMargin);
+    }
+
+    // Listen for view end changes of PhoneStatusBarView and publish that to the privacy dot
+    private View.OnLayoutChangeListener mStatusBarLayoutListener =
+            (view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                if (left != oldLeft || right != oldRight) {
+                    updateStatusBarLocation(left, right);
+                }
+            };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index c3325b1..b83039b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -68,6 +68,7 @@
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.LockPatternUtils;
@@ -154,6 +155,7 @@
     private StatusBar mStatusBar;
     private KeyguardAffordanceHelper mAffordanceHelper;
     private FalsingManager mFalsingManager;
+    @Nullable private Executor mUiExecutor;
     private boolean mUserSetupComplete;
     private boolean mPrewarmBound;
     private Messenger mPrewarmMessenger;
@@ -429,7 +431,7 @@
     }
 
     private void updateWalletVisibility() {
-        if (mDozing || !mWalletEnabled) {
+        if (mDozing || !mWalletEnabled || !mHasCard) {
             mWalletButton.setVisibility(GONE);
         } else {
             mWalletButton.setVisibility(VISIBLE);
@@ -659,6 +661,13 @@
         updateCameraVisibility();
     }
 
+    @Override
+    public void onKeyguardShowingChanged() {
+        if (mKeyguardStateController.isShowing()) {
+            queryWalletCards();
+        }
+    }
+
     private void inflateCameraPreview() {
         View previewBefore = mCameraPreview;
         boolean visibleBefore = false;
@@ -897,18 +906,20 @@
     public void initWallet(QuickAccessWalletClient client, Executor uiExecutor, boolean enabled) {
         mQuickAccessWalletClient = client;
         mWalletEnabled = enabled && client.isWalletFeatureAvailable();
+        mUiExecutor = uiExecutor;
+        queryWalletCards();
 
-        if (mWalletEnabled) {
-            queryWalletCards(uiExecutor);
-        }
         updateWalletVisibility();
     }
 
-    private void queryWalletCards(Executor uiExecutor) {
+    private void queryWalletCards() {
+        if (!mWalletEnabled || mUiExecutor == null) {
+            return;
+        }
         GetWalletCardsRequest request =
                 new GetWalletCardsRequest(1 /* cardWidth */, 1 /* cardHeight */,
                         1 /* iconSizePx */, 2 /* maxCards */);
-        mQuickAccessWalletClient.getWalletCards(uiExecutor, request, mCardRetriever);
+        mQuickAccessWalletClient.getWalletCards(mUiExecutor, request, mCardRetriever);
     }
 
     private void onWalletClick(View v) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 0694737..c22fec9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -18,7 +18,10 @@
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN;
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT;
 
+import android.animation.ValueAnimator;
 import android.annotation.ColorInt;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -47,6 +50,8 @@
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -64,8 +69,11 @@
 /**
  * The header group on Keyguard.
  */
-public class KeyguardStatusBarView extends RelativeLayout
-        implements BatteryStateChangeCallback, OnUserInfoChangedListener, ConfigurationListener {
+public class KeyguardStatusBarView extends RelativeLayout implements
+        BatteryStateChangeCallback,
+        OnUserInfoChangedListener,
+        ConfigurationListener,
+        SystemStatusAnimationCallback {
 
     private static final int LAYOUT_NONE = 0;
     private static final int LAYOUT_CUTOUT = 1;
@@ -96,6 +104,8 @@
     private ViewGroup mStatusIconArea;
     private int mLayoutState = LAYOUT_NONE;
 
+    private SystemStatusAnimationScheduler mAnimationScheduler;
+
     /**
      * Draw this many pixels into the left/right side of the cutout to optimally use the space
      */
@@ -125,6 +135,7 @@
         loadDimens();
         loadBlockList();
         mBatteryController = Dependency.get(BatteryController.class);
+        mAnimationScheduler = Dependency.get(SystemStatusAnimationScheduler.class);
     }
 
     @Override
@@ -349,6 +360,7 @@
         mIconManager = new TintedIconManager(findViewById(R.id.statusIcons));
         mIconManager.setBlockList(mBlockedIcons);
         Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager);
+        mAnimationScheduler.addCallback(this);
         onThemeChanged();
     }
 
@@ -358,6 +370,7 @@
         Dependency.get(UserInfoController.class).removeCallback(this);
         Dependency.get(StatusBarIconController.class).removeIconGroup(mIconManager);
         Dependency.get(ConfigurationController.class).removeCallback(this);
+        mAnimationScheduler.removeCallback(this);
     }
 
     @Override
@@ -509,4 +522,30 @@
             mBatteryView.dump(fd, pw, args);
         }
     }
+
+    /** SystemStatusAnimationCallback */
+    @Override
+    public void onSystemChromeAnimationStart() {
+        if (mAnimationScheduler.getAnimationState() == ANIMATING_OUT) {
+            mSystemIconsContainer.setVisibility(View.VISIBLE);
+            mSystemIconsContainer.setAlpha(0f);
+        }
+    }
+
+    @Override
+    public void onSystemChromeAnimationEnd() {
+        // Make sure the system icons are out of the way
+        if (mAnimationScheduler.getAnimationState() == ANIMATING_IN) {
+            mSystemIconsContainer.setVisibility(View.INVISIBLE);
+            mSystemIconsContainer.setAlpha(0f);
+        } else {
+            mSystemIconsContainer.setAlpha(1f);
+            mSystemIconsContainer.setVisibility(View.VISIBLE);
+        }
+    }
+
+    @Override
+    public void onSystemChromeAnimationUpdate(ValueAnimator anim) {
+        mSystemIconsContainer.setAlpha((float) anim.getAnimatedValue());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 6a35293..c4d8840 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -1997,12 +1997,35 @@
         float qsExpansionFraction = getQsExpansionFraction();
         mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
         mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
-        mScrimController.setQsPosition(qsExpansionFraction,
-                calculateQsBottomPosition(qsExpansionFraction));
+        int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction);
+        mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY);
+        setNotificationBounds(qsExpansionFraction, qsPanelBottomY);
         mNotificationStackScrollLayoutController.setQsExpansionFraction(qsExpansionFraction);
         mDepthController.setQsPanelExpansion(qsExpansionFraction);
     }
 
+    private void setNotificationBounds(float qsExpansionFraction, int qsPanelBottomY) {
+        float top = 0;
+        float bottom = 0;
+        float left = 0;
+        float right = 0;
+        if (qsPanelBottomY > 0) {
+            // notification shade is expanding/expanded
+            if (!mShouldUseSplitNotificationShade) {
+                top = qsPanelBottomY;
+                bottom = getView().getBottom();
+                left = getView().getLeft();
+                right = getView().getRight();
+            } else {
+                top = Math.min(qsPanelBottomY, mSplitShadeNotificationsTopPadding);
+                bottom = mNotificationStackScrollLayoutController.getHeight();
+                left = mNotificationStackScrollLayoutController.getLeft();
+                right = mNotificationStackScrollLayoutController.getRight();
+            }
+        }
+        mScrimController.setNotificationsBounds(left, top, right, bottom);
+    }
+
     private int calculateQsBottomPosition(float qsExpansionFraction) {
         int qsBottomY = (int) getHeaderTranslation() + mQs.getQsMinExpansionHeight();
         if (qsExpansionFraction != 0.0) {
@@ -2030,7 +2053,7 @@
 
     private float calculateNotificationsTopPadding() {
         if (mShouldUseSplitNotificationShade && !mKeyguardShowing) {
-            return mSplitShadeNotificationsTopPadding;
+            return mSplitShadeNotificationsTopPadding + mQsNotificationTopPadding;
         }
         if (mKeyguardShowing && (mQsExpandImmediate
                 || mIsExpanding && mQsExpandedWhenExpandingStarted)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 58488ef..5e9c758 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -487,6 +487,13 @@
     }
 
     /**
+     * Set bounds for notifications background, all coordinates are absolute
+     */
+    public void setNotificationsBounds(float left, float top, float right, float bottom) {
+        mNotificationsScrim.setDrawableBounds(left, top, right, bottom);
+    }
+
+    /**
      * Current state of the QuickSettings when pulling it from the top.
      *
      * @param expansionFraction From 0 to 1 where 0 means collapsed and 1 expanded.
@@ -496,7 +503,6 @@
         if (isNaN(expansionFraction)) {
             return;
         }
-        shiftNotificationsScrim(qsPanelBottomY);
         updateNotificationsScrimAlpha(expansionFraction, qsPanelBottomY);
         if (mQsExpansion != expansionFraction) {
             mQsExpansion = expansionFraction;
@@ -511,14 +517,6 @@
         }
     }
 
-    private void shiftNotificationsScrim(int qsPanelBottomY) {
-        if (qsPanelBottomY > 0) {
-            mNotificationsScrim.setTranslationY(qsPanelBottomY);
-        } else {
-            mNotificationsScrim.setTranslationY(0);
-        }
-    }
-
     private void updateNotificationsScrimAlpha(float qsExpansion, int qsPanelBottomY) {
         float newAlpha = 0;
         if (qsPanelBottomY > 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index d386ebd..ce9c2f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -208,6 +208,8 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.charging.WiredChargingRippleController;
+import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
@@ -426,6 +428,8 @@
     private final DemoModeController mDemoModeController;
     private NotificationsController mNotificationsController;
     private final OngoingCallController mOngoingCallController;
+    private final SystemStatusAnimationScheduler mAnimationScheduler;
+    private final PrivacyDotViewController mDotViewController;
 
     // expanded notifications
     // the sliding/resizing panel within the notification window
@@ -794,6 +798,8 @@
             BrightnessSlider.Factory brightnessSliderFactory,
             WiredChargingRippleController chargingRippleAnimationController,
             OngoingCallController ongoingCallController,
+            SystemStatusAnimationScheduler animationScheduler,
+            PrivacyDotViewController dotViewController,
             TunerService tunerService,
             FeatureFlags featureFlags) {
         super(context);
@@ -875,6 +881,8 @@
         mBrightnessSliderFactory = brightnessSliderFactory;
         mChargingRippleAnimationController = chargingRippleAnimationController;
         mOngoingCallController = ongoingCallController;
+        mAnimationScheduler = animationScheduler;
+        mDotViewController = dotViewController;
         mFeatureFlags = featureFlags;
 
         tunerService.addTunable(
@@ -1171,7 +1179,10 @@
                 }).getFragmentManager()
                 .beginTransaction()
                 .replace(R.id.status_bar_container,
-                        new CollapsedStatusBarFragment(mOngoingCallController),
+                        new CollapsedStatusBarFragment(
+                                mOngoingCallController,
+                                mAnimationScheduler,
+                                mDotViewController),
                         CollapsedStatusBarFragment.TAG)
                 .commit();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 2c2779e..24e6db8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -61,6 +61,8 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.charging.WiredChargingRippleController;
+import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
@@ -209,6 +211,8 @@
             BrightnessSlider.Factory brightnessSliderFactory,
             WiredChargingRippleController chargingRippleAnimationController,
             OngoingCallController ongoingCallController,
+            SystemStatusAnimationScheduler animationScheduler,
+            PrivacyDotViewController dotViewController,
             TunerService tunerService,
             FeatureFlags featureFlags) {
         return new StatusBar(
@@ -293,6 +297,8 @@
                 brightnessSliderFactory,
                 chargingRippleAnimationController,
                 ongoingCallController,
+                animationScheduler,
+                dotViewController,
                 tunerService,
                 featureFlags);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 59262cf..3252750 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -63,6 +63,7 @@
 import com.android.systemui.R.dimen;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.events.PrivacyDotViewController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.settings.FakeSettings;
 import com.android.systemui.util.settings.SecureSettings;
@@ -94,6 +95,8 @@
     private BroadcastDispatcher mBroadcastDispatcher;
     @Mock
     private UserTracker mUserTracker;
+    @Mock
+    private PrivacyDotViewController mDotViewController;
 
     @Before
     public void setup() {
@@ -116,7 +119,7 @@
         mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
 
         mScreenDecorations = spy(new ScreenDecorations(mContext, mMainHandler, mSecureSettings,
-                mBroadcastDispatcher, mTunerService, mUserTracker) {
+                mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController) {
             @Override
             public void start() {
                 super.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index 3f0831c..78c6717 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -233,9 +233,9 @@
                 TEST_UID, TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME,
                 AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
         assertEquals(2,
-                mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID)).size());
-        assertEquals(1,
-                mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID_OTHER)).size());
+                mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID), false).size());
+        assertEquals(1, mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID_OTHER),
+                false).size());
     }
 
     @Test
@@ -245,11 +245,11 @@
         mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO,
                 TEST_UID_NON_USER_SENSITIVE, mExemptedRolePkgName, true);
         assertEquals(0, mController.getActiveAppOpsForUser(
-                UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size());
+                UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE), false).size());
         mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO,
                 TEST_UID_NON_USER_SENSITIVE, SYSTEM_PKG, true);
         assertEquals(0, mController.getActiveAppOpsForUser(
-                UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size());
+                UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE), false).size());
     }
 
     @Test
@@ -441,7 +441,19 @@
     }
 
     @Test
-    public void testOnlyRecordAudioPaused() {
+    public void testPausedPhoneCallMicrophoneFilteredOut() {
+        mController.addCallback(new int[]{AppOpsManager.OP_PHONE_CALL_MICROPHONE}, mCallback);
+        mTestableLooper.processAllMessages();
+
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+
+        assertTrue(mController.getActiveAppOps().isEmpty());
+    }
+
+    @Test
+    public void testOnlyRecordAudioPhoneCallMicrophonePaused() {
         mController.addCallback(new int[]{
                 AppOpsManager.OP_RECORD_AUDIO,
                 AppOpsManager.OP_CAMERA
@@ -532,6 +544,40 @@
     }
 
     @Test
+    public void testPhoneCallMicrophoneFilteredWhenMicDisabled() {
+        mController.addCallback(
+                new int[]{AppOpsManager.OP_PHONE_CALL_MICROPHONE, AppOpsManager.OP_CAMERA},
+                mCallback);
+        mTestableLooper.processAllMessages();
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+        List<AppOpItem> list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+        assertEquals(AppOpsManager.OP_PHONE_CALL_MICROPHONE, list.get(0).getCode());
+        assertFalse(list.get(0).isDisabled());
+
+        // Add a camera op, and disable the microphone. The camera op should be the only op returned
+        mController.onSensorBlockedChanged(MICROPHONE, true);
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+        list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+        assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode());
+
+
+        // Re enable the microphone, and verify the op returns
+        mController.onSensorBlockedChanged(MICROPHONE, false);
+        mTestableLooper.processAllMessages();
+
+        list = mController.getActiveAppOps();
+        assertEquals(2, list.size());
+        int micIdx = list.get(0).getCode() == AppOpsManager.OP_CAMERA ? 1 : 0;
+        assertEquals(AppOpsManager.OP_PHONE_CALL_MICROPHONE, list.get(micIdx).getCode());
+    }
+
+    @Test
     public void testCameraFilteredWhenCameraDisabled() {
         mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA},
                 mCallback);
@@ -563,6 +609,39 @@
         assertEquals(AppOpsManager.OP_CAMERA, list.get(cameraIdx).getCode());
     }
 
+    @Test
+    public void testPhoneCallCameraFilteredWhenCameraDisabled() {
+        mController.addCallback(
+                new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_PHONE_CALL_CAMERA},
+                mCallback);
+        mTestableLooper.processAllMessages();
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_PHONE_CALL_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+        List<AppOpItem> list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+        assertEquals(AppOpsManager.OP_PHONE_CALL_CAMERA, list.get(0).getCode());
+        assertFalse(list.get(0).isDisabled());
+
+        // Add an audio op, and disable the camera. The audio op should be the only op returned
+        mController.onSensorBlockedChanged(CAMERA, true);
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+        list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+        assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode());
+
+        // Re enable the camera, and verify the op returns
+        mController.onSensorBlockedChanged(CAMERA, false);
+        mTestableLooper.processAllMessages();
+
+        list = mController.getActiveAppOps();
+        assertEquals(2, list.size());
+        int cameraIdx = list.get(0).getCode() == AppOpsManager.OP_PHONE_CALL_CAMERA ? 0 : 1;
+        assertEquals(AppOpsManager.OP_PHONE_CALL_CAMERA, list.get(cameraIdx).getCode());
+    }
+
     private class TestHandler extends AppOpsControllerImpl.H {
         TestHandler(Looper looper) {
             mController.super(looper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
index 791dd12..05a1e4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
@@ -28,6 +28,7 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.appops.AppOpsController
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.privacy.logging.PrivacyLogger
 import com.android.systemui.settings.UserTracker
@@ -43,6 +44,7 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Captor
@@ -86,6 +88,8 @@
     private lateinit var privacyLogger: PrivacyLogger
     @Mock
     private lateinit var keyguardStateController: KeyguardStateController
+    @Mock
+    private lateinit var appOpsController: AppOpsController
     @Captor
     private lateinit var dialogDismissedCaptor: ArgumentCaptor<PrivacyDialog.OnDialogDismissed>
     @Captor
@@ -131,6 +135,7 @@
                 uiExecutor,
                 privacyLogger,
                 keyguardStateController,
+                appOpsController,
                 dialogProvider
         )
     }
@@ -143,18 +148,27 @@
     }
 
     @Test
+    fun testMicMutedParameter() {
+        `when`(appOpsController.isMicMuted).thenReturn(true)
+        controller.showDialog(context)
+        backgroundExecutor.runAllReady()
+
+        verify(permissionManager).getIndicatorAppOpUsageData(true)
+    }
+
+    @Test
     fun testPermissionManagerOnlyCalledInBackgroundThread() {
         controller.showDialog(context)
-        verify(permissionManager, never()).indicatorAppOpUsageData
+        verify(permissionManager, never()).getIndicatorAppOpUsageData(anyBoolean())
         backgroundExecutor.runAllReady()
-        verify(permissionManager).indicatorAppOpUsageData
+        verify(permissionManager).getIndicatorAppOpUsageData(anyBoolean())
     }
 
     @Test
     fun testPackageManagerOnlyCalledInBackgroundThread() {
         val usage = createMockPermGroupUsage()
         `when`(usage.isPhoneCall).thenReturn(false)
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage))
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
 
         controller.showDialog(context)
         verify(packageManager, never()).getApplicationInfoAsUser(anyString(), anyInt(), anyInt())
@@ -217,7 +231,7 @@
                 isPhoneCall = false,
                 attribution = TEST_ATTRIBUTION
         )
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage))
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
 
         controller.showDialog(context)
         exhaustExecutors()
@@ -246,7 +260,7 @@
                 packageName = "${TEST_PACKAGE_NAME}_microphone",
                 permGroupName = PERM_MICROPHONE
         )
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
                 listOf(usage_microphone, usage_camera)
         )
 
@@ -269,7 +283,7 @@
                 packageName = "${TEST_PACKAGE_NAME}_recent",
                 isActive = false
         )
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
                 listOf(usage_recent, usage_active)
         )
 
@@ -292,7 +306,7 @@
                 isActive = true,
                 lastAccess = 1L
         )
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
                 listOf(usage_active, usage_active_moreRecent)
         )
         controller.showDialog(context)
@@ -319,7 +333,7 @@
                 isActive = false,
                 lastAccess = 2L
         )
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
                 listOf(usage_recent, usage_mostRecent, usage_moreRecent)
         )
 
@@ -342,7 +356,7 @@
                 permGroupName = PERM_LOCATION
         )
 
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
                 listOf(usage_camera, usage_location, usage_microphone)
         )
         `when`(privacyItemController.micCameraAvailable).thenReturn(false)
@@ -366,7 +380,7 @@
                 permGroupName = PERM_LOCATION
         )
 
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
                 listOf(usage_camera, usage_location, usage_microphone)
         )
         `when`(privacyItemController.locationAvailable).thenReturn(false)
@@ -392,7 +406,7 @@
                 permGroupName = PERM_LOCATION
         )
 
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
                 listOf(usage_camera, usage_location, usage_microphone)
         )
         `when`(privacyItemController.micCameraAvailable).thenReturn(true)
@@ -416,7 +430,7 @@
                 permGroupName = PERM_LOCATION
         )
 
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
                 listOf(usage_camera, usage_location, usage_microphone)
         )
         `when`(privacyItemController.micCameraAvailable).thenReturn(false)
@@ -433,7 +447,8 @@
         val usage_enterprise = createMockPermGroupUsage(
                 uid = generateUidForUser(ENT_USER_ID)
         )
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage_enterprise))
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+                .thenReturn(listOf(usage_enterprise))
 
         controller.showDialog(context)
         exhaustExecutors()
@@ -446,7 +461,8 @@
         val usage_other = createMockPermGroupUsage(
                 uid = generateUidForUser(ENT_USER_ID + 1)
         )
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage_other))
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+                .thenReturn(listOf(usage_other))
 
         controller.showDialog(context)
         exhaustExecutors()
@@ -514,7 +530,8 @@
     }
 
     private fun setUpDefaultMockResponses() {
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(emptyList())
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(emptyList())
+        `when`(appOpsController.isMicMuted).thenReturn(false)
 
         `when`(packageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
                 .thenAnswer {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
index bba1c6a..e4d7b1b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
@@ -46,7 +46,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyList
 import org.mockito.Captor
 import org.mockito.Mock
@@ -156,7 +156,7 @@
     fun testDistinctItems() {
         doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0),
                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0)))
-                .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+                .`when`(appOpsController).getActiveAppOps(anyBoolean())
 
         privacyItemController.addCallback(callback)
         executor.runAllReady()
@@ -168,7 +168,7 @@
     fun testSimilarItemsDifferentTimeStamp() {
         doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0),
                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 1)))
-                .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+                .`when`(appOpsController).getActiveAppOps(anyBoolean())
 
         privacyItemController.addCallback(callback)
         executor.runAllReady()
@@ -215,7 +215,7 @@
 
     @Test
     fun testMultipleCallbacksAreUpdated() {
-        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean())
 
         val otherCallback = mock(PrivacyItemController.Callback::class.java)
         privacyItemController.addCallback(callback)
@@ -233,7 +233,7 @@
 
     @Test
     fun testRemoveCallback() {
-        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean())
         val otherCallback = mock(PrivacyItemController.Callback::class.java)
         privacyItemController.addCallback(callback)
         privacyItemController.addCallback(otherCallback)
@@ -254,7 +254,7 @@
     fun testListShouldNotHaveNull() {
         doReturn(listOf(AppOpItem(AppOpsManager.OP_ACTIVATE_VPN, TEST_UID, "", 0),
                         AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0)))
-                .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+                .`when`(appOpsController).getActiveAppOps(anyBoolean())
         privacyItemController.addCallback(callback)
         executor.runAllReady()
         executor.runAllReady()
@@ -292,7 +292,7 @@
 
         doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0),
                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0)))
-                .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+                .`when`(appOpsController).getActiveAppOps(anyBoolean())
 
         privacyItemController.addCallback(callback)
         executor.runAllReady()
@@ -306,7 +306,7 @@
     @Test
     fun testNotUpdated_LocationChangeWhenOnlyMicCamera() {
         doReturn(listOf(AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0)))
-                .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+                .`when`(appOpsController).getActiveAppOps(anyBoolean())
 
         privacyItemController.addCallback(callback)
         changeLocation(false)
@@ -338,7 +338,7 @@
     fun testLogListUpdated() {
         doReturn(listOf(
                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0))
-        ).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        ).`when`(appOpsController).getActiveAppOps(anyBoolean())
 
         privacyItemController.addCallback(callback)
         executor.runAllReady()
@@ -362,10 +362,10 @@
     }
 
     @Test
-    fun testListRequestedForAllUsers() {
+    fun testListRequestedShowPaused() {
         privacyItemController.addCallback(callback)
         executor.runAllReady()
-        verify(appOpsController).getActiveAppOpsForUser(UserHandle.USER_ALL)
+        verify(appOpsController).getActiveAppOps(true)
     }
 
     @Test
@@ -377,7 +377,7 @@
         doReturn(listOf(
                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0),
                 AppOpItem(AppOpsManager.OP_CAMERA, otherUserUid, TEST_PACKAGE_NAME, 0))
-        ).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        ).`when`(appOpsController).getActiveAppOps(anyBoolean())
 
         privacyItemController.userTrackerCallback.onUserChanged(otherUser, mContext)
         executor.runAllReady()
@@ -401,7 +401,7 @@
                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0),
                 AppOpItem(AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, 0),
                 AppOpItem(AppOpsManager.OP_PHONE_CALL_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0))
-        ).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        ).`when`(appOpsController).getActiveAppOps(anyBoolean())
 
         privacyItemController.userTrackerCallback.onUserChanged(otherUser, mContext)
         executor.runAllReady()
@@ -424,7 +424,7 @@
                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0),
                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0),
                 AppOpItem(AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID, TEST_PACKAGE_NAME, 0))
-        ).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        ).`when`(appOpsController).getActiveAppOps(anyBoolean())
 
         privacyItemController.userTrackerCallback.onUserChanged(otherUser, mContext)
         executor.runAllReady()
@@ -442,7 +442,7 @@
     fun testPassageOfTimeDoesNotRemoveIndicators() {
         doReturn(listOf(
                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime)
-        )).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        )).`when`(appOpsController).getActiveAppOps(anyBoolean())
 
         privacyItemController.addCallback(callback)
 
@@ -458,12 +458,12 @@
         // Start with some element at time 0
         doReturn(listOf(
                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime)
-        )).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        )).`when`(appOpsController).getActiveAppOps(anyBoolean())
         privacyItemController.addCallback(callback)
         executor.runAllReady()
 
         // Then remove it at time HOLD + 1
-        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean())
         fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS + 1)
 
         verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
@@ -481,12 +481,12 @@
         // Start with some element at time 0
         doReturn(listOf(
                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime)
-        )).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        )).`when`(appOpsController).getActiveAppOps(anyBoolean())
         privacyItemController.addCallback(callback)
         executor.runAllReady()
 
         // Then remove it at time HOLD - 1
-        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean())
         fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS - 1)
 
         verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
@@ -504,12 +504,12 @@
         // Start with some element at time 0
         doReturn(listOf(
                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime)
-        )).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        )).`when`(appOpsController).getActiveAppOps(anyBoolean())
         privacyItemController.addCallback(callback)
         executor.runAllReady()
 
         // Then remove it at time HOLD - 1
-        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean())
         fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS - 1)
 
         verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
@@ -525,6 +525,30 @@
         assertTrue(privacyItemController.privacyList.isEmpty())
     }
 
+    @Test
+    fun testPausedElementsAreRemoved() {
+        val item = AppOpItem(
+                AppOpsManager.OP_RECORD_AUDIO,
+                TEST_UID,
+                TEST_PACKAGE_NAME,
+                elapsedTime
+        )
+        `when`(appOpsController.getActiveAppOps(anyBoolean())).thenReturn(listOf(item))
+        privacyItemController.addCallback(callback)
+        executor.runAllReady()
+
+        item.isDisabled = true
+        fakeClock.advanceTime(1)
+        verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
+        argCaptorCallback.value.onActiveStateChanged(
+                AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, false)
+
+        executor.runAllReady()
+
+        verify(callback).onPrivacyItemsChanged(emptyList())
+        assertTrue(privacyItemController.privacyList.isEmpty())
+    }
+
     private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value)
     private fun changeLocation(value: Boolean?) = changeProperty(LOCATION_INDICATOR, value)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
index acedf59..4f88599 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -81,7 +81,7 @@
 
         mTestableLooper.runWithLooper(() -> {
             mQsPanel = new QSPanel(mContext, null);
-            mQsPanel.initialize(false);
+            mQsPanel.initialize();
             mQsPanel.onFinishInflate();
             // Provides a parent with non-zero size for QSPanel
             mParentView = new FrameLayout(mContext);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
index 62cc9b7..3d53062 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
@@ -49,7 +49,7 @@
         MockitoAnnotations.initMocks(this);
 
         TestableLooper.get(this).runWithLooper(() -> mTileAdapter =
-                new TileAdapter(mContext, mQSTileHost, new UiEventLoggerFake(), /* qsFlag */false));
+                new TileAdapter(mContext, mQSTileHost, new UiEventLoggerFake()));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java
index 87a7757..c2e58ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java
@@ -19,6 +19,7 @@
 import static junit.framework.Assert.assertEquals;
 
 import android.graphics.Color;
+import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.testing.AndroidTestingRunner;
@@ -89,4 +90,17 @@
         mView.setTint(tint);
         assertEquals(mView.getTint(), tint);
     }
+
+    @Test
+    public void setDrawableBounds_propagatesToDrawable() {
+        ColorDrawable drawable = new ColorDrawable();
+        Rect expectedBounds = new Rect(100, 100, 100, 100);
+        mView.setDrawable(drawable);
+        mView.setDrawableBounds(100, 100, 100, 100);
+
+        assertEquals(expectedBounds, drawable.getBounds());
+        // set bounds that are different from expected drawable bounds
+        mView.onLayout(true, 200, 200, 200, 200);
+        assertEquals(expectedBounds, drawable.getBounds());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
index 67fd5eb..929a7e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
@@ -37,6 +37,8 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiBaseFragmentTest;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
 
@@ -58,6 +60,8 @@
     private View mCenteredNotificationAreaView;
     private StatusBarStateController mStatusBarStateController;
     private OngoingCallController mOngoingCallController;
+    private SystemStatusAnimationScheduler mAnimationScheduler;
+    private PrivacyDotViewController mDotViewController;
 
     public CollapsedStatusBarFragmentTest() {
         super(CollapsedStatusBarFragment.class);
@@ -212,6 +216,11 @@
     @Override
     protected Fragment instantiate(Context context, String className, Bundle arguments) {
         mOngoingCallController = mock(OngoingCallController.class);
-        return new CollapsedStatusBarFragment(mOngoingCallController);
+        mAnimationScheduler = mock(SystemStatusAnimationScheduler.class);
+        mDotViewController = mock(PrivacyDotViewController.class);
+        return new CollapsedStatusBarFragment(
+                mOngoingCallController,
+                mAnimationScheduler,
+                mDotViewController);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 08d6d2d..3989dfa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -113,6 +113,8 @@
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.charging.WiredChargingRippleController;
+import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -263,6 +265,8 @@
     @Mock private BrightnessSlider.Factory mBrightnessSliderFactory;
     @Mock private WiredChargingRippleController mWiredChargingRippleController;
     @Mock private OngoingCallController mOngoingCallController;
+    @Mock private SystemStatusAnimationScheduler mAnimationScheduler;
+    @Mock private PrivacyDotViewController mDotViewController;
     @Mock private TunerService mTunerService;
     @Mock private FeatureFlags mFeatureFlags;
     private ShadeController mShadeController;
@@ -429,6 +433,8 @@
                 mBrightnessSliderFactory,
                 mWiredChargingRippleController,
                 mOngoingCallController,
+                mAnimationScheduler,
+                mDotViewController,
                 mTunerService,
                 mFeatureFlags);
         when(mKeyguardViewMediator.registerStatusBar(any(StatusBar.class), any(ViewGroup.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 4471778..40439a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -36,7 +36,6 @@
 
 import android.app.Instrumentation;
 import android.net.ConnectivityManager;
-import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
@@ -323,34 +322,37 @@
 
     public void setConnectivityViaCallbackInNetworkControllerForVcn(
             int networkType, boolean validated, boolean isConnected, VcnTransportInfo info) {
-        mNetCapabilities.setTransportInfo(info);
-        setConnectivityCommon(networkType, validated, isConnected);
-        mDefaultCallbackInNetworkController.onCapabilitiesChanged(
-                mNetwork, new NetworkCapabilities(mNetCapabilities));
+        final NetworkCapabilities.Builder builder =
+                new NetworkCapabilities.Builder(mNetCapabilities);
+        builder.setTransportInfo(info);
+        setConnectivityCommon(builder, networkType, validated, isConnected);
+        mDefaultCallbackInNetworkController.onCapabilitiesChanged(mNetwork, builder.build());
     }
 
     public void setConnectivityViaCallbackInNetworkController(
             int networkType, boolean validated, boolean isConnected, WifiInfo wifiInfo) {
+        final NetworkCapabilities.Builder builder =
+                new NetworkCapabilities.Builder(mNetCapabilities);
         if (networkType == NetworkCapabilities.TRANSPORT_WIFI) {
-            mNetCapabilities.setTransportInfo(wifiInfo);
+            builder.setTransportInfo(wifiInfo);
         }
-        setConnectivityCommon(networkType, validated, isConnected);
-        mDefaultCallbackInNetworkController.onCapabilitiesChanged(
-                mNetwork, new NetworkCapabilities(mNetCapabilities));
+        setConnectivityCommon(builder, networkType, validated, isConnected);
+        mDefaultCallbackInNetworkController.onCapabilitiesChanged(mNetwork, builder.build());
     }
 
     public void setConnectivityViaCallbackInWifiTracker(
             int networkType, boolean validated, boolean isConnected, WifiInfo wifiInfo) {
+        final NetworkCapabilities.Builder builder =
+                new NetworkCapabilities.Builder(mNetCapabilities);
         if (networkType == NetworkCapabilities.TRANSPORT_WIFI) {
-            mNetCapabilities.setTransportInfo(wifiInfo);
+            builder.setTransportInfo(wifiInfo);
         }
-        setConnectivityCommon(networkType, validated, isConnected);
+        setConnectivityCommon(builder, networkType, validated, isConnected);
         if (networkType == NetworkCapabilities.TRANSPORT_WIFI) {
             if (isConnected) {
-                mNetworkCallback.onAvailable(mNetwork,
-                        new NetworkCapabilities(mNetCapabilities), new LinkProperties(), false);
-                mNetworkCallback.onCapabilitiesChanged(
-                        mNetwork, new NetworkCapabilities(mNetCapabilities));
+                final NetworkCapabilities newCap = builder.build();
+                mNetworkCallback.onAvailable(mNetwork);
+                mNetworkCallback.onCapabilitiesChanged(mNetwork, newCap);
             } else {
                 mNetworkCallback.onLost(mNetwork);
             }
@@ -359,16 +361,16 @@
 
     public void setConnectivityViaCallbackInWifiTrackerForVcn(
             int networkType, boolean validated, boolean isConnected, VcnTransportInfo info) {
-        mNetCapabilities.setTransportInfo(info);
-        setConnectivityCommon(networkType, validated, isConnected);
+        final NetworkCapabilities.Builder builder =
+                new NetworkCapabilities.Builder(mNetCapabilities);
+        builder.setTransportInfo(info);
+        setConnectivityCommon(builder, networkType, validated, isConnected);
         if (networkType == NetworkCapabilities.TRANSPORT_CELLULAR) {
             if (isConnected) {
-                mNetworkCallback.onAvailable(mNetwork,
-                        new NetworkCapabilities(mNetCapabilities), new LinkProperties(), false);
-                mNetworkCallback.onCapabilitiesChanged(
-                        mNetwork, new NetworkCapabilities(mNetCapabilities));
-                mDefaultCallbackInWifiTracker.onCapabilitiesChanged(
-                        mNetwork, new NetworkCapabilities(mNetCapabilities));
+                final NetworkCapabilities newCap = builder.build();
+                mNetworkCallback.onAvailable(mNetwork);
+                mNetworkCallback.onCapabilitiesChanged(mNetwork, newCap);
+                mDefaultCallbackInWifiTracker.onCapabilitiesChanged(mNetwork, newCap);
             } else {
                 mNetworkCallback.onLost(mNetwork);
             }
@@ -377,26 +379,28 @@
 
     public void setConnectivityViaDefaultCallbackInWifiTracker(
             int networkType, boolean validated, boolean isConnected, WifiInfo wifiInfo) {
+        final NetworkCapabilities.Builder builder =
+                new NetworkCapabilities.Builder(mNetCapabilities);
         if (networkType == NetworkCapabilities.TRANSPORT_WIFI) {
-            mNetCapabilities.setTransportInfo(wifiInfo);
+            builder.setTransportInfo(wifiInfo);
         }
-        setConnectivityCommon(networkType, validated, isConnected);
+        setConnectivityCommon(builder, networkType, validated, isConnected);
         mDefaultCallbackInWifiTracker.onCapabilitiesChanged(
-                mNetwork, new NetworkCapabilities(mNetCapabilities));
+                mNetwork, builder.build());
     }
 
-    private void setConnectivityCommon(
+    private static void setConnectivityCommon(NetworkCapabilities.Builder builder,
         int networkType, boolean validated, boolean isConnected){
         // TODO: Separate out into several NetworkCapabilities.
         if (isConnected) {
-            mNetCapabilities.addTransportType(networkType);
+            builder.addTransportType(networkType);
         } else {
-            mNetCapabilities.removeTransportType(networkType);
+            builder.removeTransportType(networkType);
         }
         if (validated) {
-            mNetCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
         } else {
-            mNetCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+            builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index ed87a40..c38a547 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -202,8 +202,8 @@
     @Test
     public void testNetworkRequest() {
         verify(mConnectivityManager, times(1)).registerNetworkCallback(argThat(
-                (NetworkRequest request) -> request.networkCapabilities.getUids() == null
-                        && request.networkCapabilities.getCapabilities().length == 0
+                (NetworkRequest request) ->
+                        request.equals(new NetworkRequest.Builder().clearCapabilities().build())
                 ), any(NetworkCallback.class));
     }
 
diff --git a/services/core/java/com/android/server/BluetoothAirplaneModeListener.java b/services/core/java/com/android/server/BluetoothAirplaneModeListener.java
index aa56da5..197321f 100644
--- a/services/core/java/com/android/server/BluetoothAirplaneModeListener.java
+++ b/services/core/java/com/android/server/BluetoothAirplaneModeListener.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import android.annotation.RequiresPermission;
 import android.content.Context;
 import android.database.ContentObserver;
 import android.os.Handler;
@@ -106,6 +107,7 @@
     }
 
     @VisibleForTesting
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     void handleAirplaneModeChange() {
         if (shouldSkipAirplaneModeChange()) {
             Log.i(TAG, "Ignore airplane mode change");
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 09cfac0..feed220 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -23,6 +23,8 @@
 import static android.os.UserHandle.USER_SYSTEM;
 
 import android.Manifest;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
@@ -304,6 +306,19 @@
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                 "Need BLUETOOTH_PRIVILEGED permission");
 
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return onFactoryResetInternal();
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
+    private boolean onFactoryResetInternal() {
         // Wait for stable state if bluetooth is temporary state.
         int state = getState();
         if (state == BluetoothAdapter.STATE_BLE_TURNING_ON
@@ -343,6 +358,7 @@
         return false;
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public void onAirplaneModeChanged() {
         synchronized (this) {
             if (isBluetoothPersistedStateOn()) {
@@ -707,9 +723,6 @@
     }
 
     public void registerStateChangeCallback(IBluetoothStateChangeCallback callback) {
-        if (!checkConnectPermissionForPreflight(mContext)) {
-            return;
-        }
         if (callback == null) {
             Slog.w(TAG, "registerStateChangeCallback: Callback is null!");
             return;
@@ -720,9 +733,6 @@
     }
 
     public void unregisterStateChangeCallback(IBluetoothStateChangeCallback callback) {
-        if (!checkConnectPermissionForPreflight(mContext)) {
-            return;
-        }
         if (callback == null) {
             Slog.w(TAG, "unregisterStateChangeCallback: Callback is null!");
             return;
@@ -935,6 +945,7 @@
         return appCount;
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     private boolean checkBluetoothPermissions(String packageName, boolean requireForeground) {
         if (isBluetoothDisallowed()) {
             if (DBG) {
@@ -990,6 +1001,7 @@
         return true;
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean disableBle(String packageName, IBinder token) throws RemoteException {
         if (!checkBluetoothPermissions(packageName, false)) {
             if (DBG) {
@@ -1040,6 +1052,7 @@
      * Call IBluetooth.onLeServiceUp() to continue if Bluetooth should be on,
      * call IBluetooth.onBrEdrDown() to disable if Bluetooth should be off.
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     private void continueFromBleOnState() {
         if (DBG) {
             Slog.d(TAG, "continueFromBleOnState()");
@@ -1072,6 +1085,10 @@
      * Inform BluetoothAdapter instances that BREDR part is down
      * and turn off all service and stack if no LE app needs it
      */
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     private void sendBrEdrDownCallback() {
         if (DBG) {
             Slog.d(TAG, "Calling sendBrEdrDownCallback callbacks");
@@ -1265,12 +1282,14 @@
      *
      * @hide
      */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     private boolean checkBluetoothPermissionWhenWirelessConsentRequired() {
         int result = mContext.checkCallingPermission(
                 android.Manifest.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED);
         return result == PackageManager.PERMISSION_GRANTED;
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public void unbindAndFinish() {
         if (DBG) {
             Slog.d(TAG, "unbindAndFinish(): " + mBluetooth + " mBinding = " + mBinding
@@ -2300,6 +2319,10 @@
             }
         }
 
+        @RequiresPermission(allOf = {
+                android.Manifest.permission.BLUETOOTH_CONNECT,
+                android.Manifest.permission.BLUETOOTH_PRIVILEGED
+        })
         private void restartForReason(int reason) {
             try {
                 mBluetoothLock.readLock().lock();
@@ -2376,6 +2399,7 @@
         }
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     private void handleEnable(boolean quietMode) {
         mQuietEnable = quietMode;
 
@@ -2418,6 +2442,7 @@
         return true;
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     private void handleDisable() {
         try {
             mBluetoothLock.readLock().lock();
@@ -2475,6 +2500,10 @@
         mContext.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_CONNECT);
     }
 
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     private void bluetoothStateChangeHandler(int prevState, int newState) {
         boolean isStandardBroadcast = true;
         if (prevState == newState) { // No change. Nothing to do.
@@ -2615,6 +2644,10 @@
         }
     }
 
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     private void recoverBluetoothServiceFromError(boolean clearBle) {
         Slog.e(TAG, "recoverBluetoothServiceFromError");
         try {
@@ -2848,6 +2881,7 @@
      *
      * <p>Should be used in situations where the app op should not be noted.
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     private static boolean checkConnectPermissionForPreflight(Context context) {
         int permissionCheckResult = PermissionChecker.checkCallingOrSelfPermissionForPreflight(
                 context, BLUETOOTH_CONNECT);
diff --git a/services/core/java/com/android/server/BluetoothModeChangeHelper.java b/services/core/java/com/android/server/BluetoothModeChangeHelper.java
index 242fa84..3642e4d 100644
--- a/services/core/java/com/android/server/BluetoothModeChangeHelper.java
+++ b/services/core/java/com/android/server/BluetoothModeChangeHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import android.annotation.RequiresPermission;
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothHearingAid;
@@ -101,6 +102,7 @@
     }
 
     @VisibleForTesting
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public void onAirplaneModeChanged(BluetoothManagerService managerService) {
         managerService.onAirplaneModeChanged();
     }
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 9f91dd6..483250a 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -174,9 +174,9 @@
 # Disk usage stats for verifying quota correctness
 3121 pm_package_stats (manual_time|2|3),(quota_time|2|3),(manual_data|2|2),(quota_data|2|2),(manual_cache|2|2),(quota_cache|2|2)
 # Snapshot statistics
-3130 pm_snapshot_stats (build_count|1|1),(reuse_count|1|1),(big_builds|1|1),(quick_rebuilds|1|1),(max_build_time|1|3),(cumm_build_time|1|3)
+3130 pm_snapshot_stats (build_count|1|1),(reuse_count|1|1),(big_builds|1|1),(short_lived|1|1),(max_build_time|1|3),(cumm_build_time|2|3)
 # Snapshot rebuild instance
-3131 pm_snapshot_rebuild (build_time|1|3),(elapsed|1|3)
+3131 pm_snapshot_rebuild (build_time|1|3),(lifetime|1|3)
 
 # ---------------------------
 # InputMethodManagerService.java
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
index df6ab5d..3ba4c34 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -19,9 +19,10 @@
 import static android.Manifest.permission.MANAGE_SENSOR_PRIVACY;
 import static android.app.ActivityManager.RunningServiceInfo;
 import static android.app.ActivityManager.RunningTaskInfo;
-import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
 import static android.content.Intent.EXTRA_PACKAGE_NAME;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.hardware.SensorPrivacyManager.EXTRA_SENSOR;
@@ -140,11 +141,15 @@
     private final UserManagerInternal mUserManagerInternal;
     private final ActivityManager mActivityManager;
     private final ActivityTaskManager mActivityTaskManager;
+    private final AppOpsManager mAppOpsManager;
+
+    private final IBinder mAppOpsRestrictionToken = new Binder();
 
     private SensorPrivacyManagerInternalImpl mSensorPrivacyManagerInternal;
 
     public SensorPrivacyService(Context context) {
         super(context);
+        mAppOpsManager = context.getSystemService(AppOpsManager.class);
         mUserManagerInternal = getLocalService(UserManagerInternal.class);
         mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl(context);
         mActivityManager = context.getSystemService(ActivityManager.class);
@@ -194,10 +199,20 @@
                 }
             }
 
+            for (int i = 0; i < mIndividualEnabled.size(); i++) {
+                int userId = mIndividualEnabled.keyAt(i);
+                SparseBooleanArray userIndividualEnabled =
+                        mIndividualEnabled.get(i);
+                for (int j = 0; j < userIndividualEnabled.size(); j++) {
+                    int sensor = userIndividualEnabled.keyAt(i);
+                    boolean enabled = userIndividualEnabled.valueAt(j);
+                    setUserRestriction(userId, sensor, enabled);
+                }
+            }
+
             int[] micAndCameraOps = new int[]{OP_RECORD_AUDIO, OP_CAMERA};
-            AppOpsManager appOpsManager = mContext.getSystemService(AppOpsManager.class);
-            appOpsManager.startWatchingNoted(micAndCameraOps, this);
-            appOpsManager.startWatchingStarted(micAndCameraOps, this);
+            mAppOpsManager.startWatchingNoted(micAndCameraOps, this);
+            mAppOpsManager.startWatchingStarted(micAndCameraOps, this);
 
             mContext.registerReceiver(new BroadcastReceiver() {
                 @Override
@@ -221,7 +236,7 @@
         public void onOpNoted(int code, int uid, String packageName,
                 String attributionTag, @AppOpsManager.OpFlags int flags,
                 @AppOpsManager.Mode int result) {
-            if (result != MODE_ALLOWED || (flags & AppOpsManager.OP_FLAGS_ALL_TRUSTED) == 0) {
+            if (result != MODE_IGNORED || (flags & AppOpsManager.OP_FLAGS_ALL_TRUSTED) == 0) {
                 return;
             }
 
@@ -1125,6 +1140,9 @@
             mSensorPrivacyManagerInternal.dispatch(userId, sensor, enabled);
             SparseArray<RemoteCallbackList<ISensorPrivacyListener>> listenersForUser =
                     mIndividualSensorListeners.get(userId);
+
+            setUserRestriction(userId, sensor, enabled);
+
             if (listenersForUser == null) {
                 return;
             }
@@ -1152,6 +1170,18 @@
         }
     }
 
+    private void setUserRestriction(int userId, int sensor, boolean enabled) {
+        if (sensor == CAMERA) {
+            mAppOpsManager.setUserRestrictionForUser(OP_CAMERA, enabled,
+                    mAppOpsRestrictionToken, new String[]{}, userId);
+        } else if (sensor == MICROPHONE) {
+            mAppOpsManager.setUserRestrictionForUser(OP_RECORD_AUDIO, enabled,
+                    mAppOpsRestrictionToken, new String[]{}, userId);
+            mAppOpsManager.setUserRestrictionForUser(OP_RECORD_AUDIO_HOTWORD, enabled,
+                    mAppOpsRestrictionToken, new String[]{}, userId);
+        }
+    }
+
     private final class DeathRecipient implements IBinder.DeathRecipient {
 
         private ISensorPrivacyListener mListener;
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 98bfa28..2d486c4 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2551,11 +2551,12 @@
      * Send a notification to registrants that the configs of physical channel has changed for
      * a particular subscription.
      *
+     * @param phoneId the phone id.
      * @param subId the subId
      * @param configs a list of {@link PhysicalChannelConfig}, the configs of physical channel.
      */
-    public void notifyPhysicalChannelConfigForSubscriber(
-            int subId, List<PhysicalChannelConfig> configs) {
+    public void notifyPhysicalChannelConfigForSubscriber(int phoneId, int subId,
+            List<PhysicalChannelConfig> configs) {
         if (!checkNotifyPermission("notifyPhysicalChannelConfig()")) {
             return;
         }
@@ -2567,7 +2568,6 @@
         }
 
         synchronized (mRecords) {
-            int phoneId = SubscriptionManager.getPhoneId(subId);
             if (validatePhoneId(phoneId)) {
                 mPhysicalChannelConfigs.set(phoneId, configs);
                 for (Record r : mRecords) {
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index b103def..b7ef10a 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -199,13 +199,12 @@
         @Override
         public void onPropertiesChanged(Properties properties) {
             synchronized (mDeviceConfigLock) {
-                for (String key : properties.getKeyset()) {
+                for (final String packageName : properties.getKeyset()) {
                     try {
                         // Check if the package is installed before caching it.
-                        final String packageName = keyToPackageName(key);
                         mPackageManager.getPackageInfo(packageName, 0);
                         final GamePackageConfiguration config =
-                                GamePackageConfiguration.fromProperties(key, properties);
+                                GamePackageConfiguration.fromProperties(packageName, properties);
                         if (config.isValid()) {
                             putConfig(config);
                         } else {
@@ -290,8 +289,8 @@
         private final String mPackageName;
         private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs;
 
-        private GamePackageConfiguration(String keyName) {
-            mPackageName = keyToPackageName(keyName);
+        private GamePackageConfiguration(String packageName) {
+            mPackageName = packageName;
             mModeConfigs = new ArrayMap<>();
         }
 
@@ -563,9 +562,9 @@
         }
     }
 
-    private void loadDeviceConfigLocked() {
+    void loadDeviceConfigLocked() {
         final List<PackageInfo> packages = mPackageManager.getInstalledPackages(0);
-        final String[] packageNames = packages.stream().map(e -> packageNameToKey(e.packageName))
+        final String[] packageNames = packages.stream().map(e -> e.packageName)
                 .toArray(String[]::new);
         synchronized (mDeviceConfigLock) {
             final Properties properties = DeviceConfig.getProperties(
@@ -680,8 +679,7 @@
                         case ACTION_PACKAGE_CHANGED:
                             synchronized (mDeviceConfigLock) {
                                 Properties properties = DeviceConfig.getProperties(
-                                        DeviceConfig.NAMESPACE_GAME_OVERLAY,
-                                        packageNameToKey(packageName));
+                                        DeviceConfig.NAMESPACE_GAME_OVERLAY, packageName);
                                 for (String key : properties.getKeyset()) {
                                     GamePackageConfiguration config =
                                             GamePackageConfiguration.fromProperties(key,
@@ -692,7 +690,9 @@
                             break;
                         case ACTION_PACKAGE_REMOVED:
                             disableCompatScale(packageName);
-                            mConfigs.remove(packageName);
+                            synchronized (mDeviceConfigLock) {
+                                mConfigs.remove(packageName);
+                            }
                             break;
                         default:
                             // do nothing
@@ -710,23 +710,6 @@
         mDeviceConfigListener = new DeviceConfigListener();
     }
 
-    /**
-     * Valid package name characters are [a-zA-Z0-9_] with a '.' delimiter. Policy keys can only use
-     * [a-zA-Z0-9_] so we must handle periods. We do this by appending a '_' to any existing
-     * sequence of '_', then we replace all '.' chars with '_';
-     */
-    private static String packageNameToKey(String name) {
-        return name.replaceAll("(_+)", "_$1").replaceAll("\\.", "_");
-    }
-
-    /**
-     * Replace the last '_' in a sequence with '.' (this can be one or more chars), then replace the
-     * resulting special case '_.' with just '_' to get the original package name.
-     */
-    private static String keyToPackageName(String key) {
-        return key.replaceAll("(_)(?!\\1)", ".").replaceAll("_\\.", "_");
-    }
-
     private String dumpDeviceConfigs() {
         StringBuilder out = new StringBuilder();
         for (String key : mConfigs.keySet()) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 282a12d..96bb73f 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -503,7 +503,7 @@
         }
     }
 
-    private static final class BtDeviceConnectionInfo {
+    /*package*/ static final class BtDeviceConnectionInfo {
         final @NonNull BluetoothDevice mDevice;
         final @AudioService.BtProfileConnectionState int mState;
         final int mProfile;
@@ -520,6 +520,14 @@
             mVolume = vol;
         }
 
+        BtDeviceConnectionInfo(@NonNull BtDeviceConnectionInfo info) {
+            mDevice = info.mDevice;
+            mState = info.mState;
+            mProfile = info.mProfile;
+            mSupprNoisy = info.mSupprNoisy;
+            mVolume = info.mVolume;
+        }
+
         // redefine equality op so we can match messages intended for this device
         @Override
         public boolean equals(Object o) {
@@ -541,18 +549,19 @@
         }
     }
 
-    /*package*/ void postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
-            @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
-            int profile, boolean suppressNoisyIntent, int a2dpVolume) {
-        final BtDeviceConnectionInfo info = new BtDeviceConnectionInfo(device, state, profile,
-                suppressNoisyIntent, a2dpVolume);
-
-        final String name = TextUtils.emptyIfNull(device.getName());
+    /**
+     * will block on mDeviceStateLock, which is held during an A2DP (dis) connection
+     * not just a simple message post
+     * @param info struct with the (dis)connection information
+     */
+    /*package*/ void queueBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+            @NonNull BtDeviceConnectionInfo info) {
+        final String name = TextUtils.emptyIfNull(info.mDevice.getName());
         new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE + MediaMetrics.SEPARATOR
                 + "postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent")
-                .set(MediaMetrics.Property.STATE, state == BluetoothProfile.STATE_CONNECTED
+                .set(MediaMetrics.Property.STATE, info.mState == BluetoothProfile.STATE_CONNECTED
                         ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED)
-                .set(MediaMetrics.Property.INDEX, a2dpVolume)
+                .set(MediaMetrics.Property.INDEX, info.mVolume)
                 .set(MediaMetrics.Property.NAME, name)
                 .record();
 
@@ -562,10 +571,10 @@
             // when receiving a request to change the connection state of a device, this last
             // request is the source of truth, so cancel all previous requests that are already in
             // the handler
-            removeScheduledA2dpEvents(device);
+            removeScheduledA2dpEvents(info.mDevice);
 
             sendLMsgNoDelay(
-                    state == BluetoothProfile.STATE_CONNECTED
+                    info.mState == BluetoothProfile.STATE_CONNECTED
                             ? MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION
                             : MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION,
                     SENDMSG_QUEUE, info);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9707ace..edacd40 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -310,6 +310,8 @@
     private static final int MSG_UPDATE_A11Y_SERVICE_UIDS = 35;
     private static final int MSG_UPDATE_AUDIO_MODE = 36;
     private static final int MSG_RECORDING_CONFIG_CHANGE = 37;
+    private static final int MSG_SET_A2DP_DEV_CONNECTION_STATE = 38;
+    private static final int MSG_A2DP_DEV_CONFIG_CHANGE = 39;
 
     // start of messages handled under wakelock
     //   these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
@@ -6114,7 +6116,7 @@
      * See AudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent()
      */
     public void setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
-            @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+            @NonNull BluetoothDevice device, @BtProfileConnectionState int state,
             int profile, boolean suppressNoisyIntent, int a2dpVolume) {
         if (device == null) {
             throw new IllegalArgumentException("Illegal null device");
@@ -6124,8 +6126,13 @@
             throw new IllegalArgumentException("Illegal BluetoothProfile state for device "
                     + " (dis)connection, got " + state);
         }
-        mDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(device, state,
-                profile, suppressNoisyIntent, a2dpVolume);
+
+        AudioDeviceBroker.BtDeviceConnectionInfo info =
+                new AudioDeviceBroker.BtDeviceConnectionInfo(device, state,
+                        profile, suppressNoisyIntent, a2dpVolume);
+        sendMsg(mAudioHandler, MSG_SET_A2DP_DEV_CONNECTION_STATE, SENDMSG_QUEUE,
+                0 /*arg1*/, 0 /*arg2*/,
+                /*obj*/ info, 0 /*delay*/);
     }
 
     /** only public for mocking/spying, do not call outside of AudioService */
@@ -6143,7 +6150,8 @@
         if (device == null) {
             throw new IllegalArgumentException("Illegal null device");
         }
-        mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device);
+        sendMsg(mAudioHandler, MSG_A2DP_DEV_CONFIG_CHANGE, SENDMSG_QUEUE, 0, 0,
+                /*obj*/ device, /*delay*/ 0);
     }
 
     private static final Set<Integer> DEVICE_MEDIA_UNMUTED_ON_PLUG_SET;
@@ -7505,6 +7513,15 @@
                         onUpdateAudioMode(msg.arg1, msg.arg2, (String) msg.obj, false /*force*/);
                     }
                     break;
+
+                case MSG_SET_A2DP_DEV_CONNECTION_STATE:
+                    mDeviceBroker.queueBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+                            (AudioDeviceBroker.BtDeviceConnectionInfo) msg.obj);
+                    break;
+
+                case MSG_A2DP_DEV_CONFIG_CHANGE:
+                    mDeviceBroker.postBluetoothA2dpDeviceConfigChange((BluetoothDevice) msg.obj);
+                    break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index c57d5af..52e8edf 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -454,8 +454,10 @@
         }
         final BluetoothDevice btDevice = deviceList.get(0);
         // the device is guaranteed CONNECTED
-        mDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(btDevice,
-                BluetoothA2dp.STATE_CONNECTED, BluetoothProfile.A2DP_SINK, true, -1);
+        mDeviceBroker.queueBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+                new AudioDeviceBroker.BtDeviceConnectionInfo(btDevice,
+                    BluetoothA2dp.STATE_CONNECTED, BluetoothProfile.A2DP_SINK,
+                        true, -1));
     }
 
     /*package*/ synchronized void onA2dpSinkProfileConnected(BluetoothProfile profile) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index d56fd12..b795b54 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -375,12 +375,12 @@
         }
 
         @Override
-        public void onFeaturesRetrieved(byte[] features, int enrollmentId) {
+        public void onFeaturesRetrieved(byte[] features) {
 
         }
 
         @Override
-        public void onFeatureSet(int enrollmentId, byte feature) {
+        public void onFeatureSet(byte feature) {
 
         }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
index c63af7e..f1bfd53 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
@@ -32,6 +32,7 @@
  */
 public class TestHal extends IFace.Stub {
     private static final String TAG = "face.aidl.TestHal";
+
     @Override
     public SensorProps[] getSensorProps() {
         Slog.w(TAG, "getSensorProps");
@@ -102,16 +103,16 @@
             }
 
             @Override
-            public void getFeatures(int enrollmentId) throws RemoteException {
+            public void getFeatures() throws RemoteException {
                 Slog.w(TAG, "getFeatures");
-                cb.onFeaturesRetrieved(new byte[0], enrollmentId);
+                cb.onFeaturesRetrieved(new byte[0]);
             }
 
             @Override
-            public void setFeature(HardwareAuthToken hat, int enrollmentId,
-                    byte feature, boolean enabled) throws RemoteException {
+            public void setFeature(HardwareAuthToken hat, byte feature, boolean enabled)
+                    throws RemoteException {
                 Slog.w(TAG, "setFeature");
-                cb.onFeatureSet(enrollmentId, feature);
+                cb.onFeatureSet(feature);
             }
 
             @Override
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
index 045e06d0..2ffc62a 100644
--- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -178,8 +178,9 @@
     }
 
     /** Logs that a provider has entered or exited stationary throttling. */
-    public void logProviderStationaryThrottled(String provider, boolean throttled) {
-        addLogEvent(EVENT_PROVIDER_STATIONARY_THROTTLED, provider, throttled);
+    public void logProviderStationaryThrottled(String provider, boolean throttled,
+            ProviderRequest request) {
+        addLogEvent(EVENT_PROVIDER_STATIONARY_THROTTLED, provider, throttled, request);
     }
 
     /** Logs that the location power save mode has changed. */
@@ -217,7 +218,7 @@
                         (Integer) args[1], (CallerIdentity) args[2]);
             case EVENT_PROVIDER_STATIONARY_THROTTLED:
                 return new ProviderStationaryThrottledEvent(timeDelta, (String) args[0],
-                        (Boolean) args[1]);
+                        (Boolean) args[1], (ProviderRequest) args[2]);
             case EVENT_LOCATION_POWER_SAVE_MODE_CHANGE:
                 return new LocationPowerSaveModeEvent(timeDelta, (Integer) args[0]);
             default:
@@ -355,17 +356,19 @@
     private static final class ProviderStationaryThrottledEvent extends ProviderEvent {
 
         private final boolean mStationaryThrottled;
+        private final ProviderRequest mRequest;
 
         ProviderStationaryThrottledEvent(long timeDelta, String provider,
-                boolean stationaryThrottled) {
+                boolean stationaryThrottled, ProviderRequest request) {
             super(timeDelta, provider);
             mStationaryThrottled = stationaryThrottled;
+            mRequest = request;
         }
 
         @Override
         public String getLogString() {
             return mProvider + " provider stationary/idle " + (mStationaryThrottled ? "throttled"
-                    : "unthrottled");
+                    : "unthrottled") + ", request = " + mRequest;
         }
     }
 
diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
index ab7e526..22a675a 100644
--- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
@@ -206,7 +206,7 @@
                 if (D) {
                     Log.d(TAG, mName + " provider stationary throttled");
                 }
-                EVENT_LOG.logProviderStationaryThrottled(mName, true);
+                EVENT_LOG.logProviderStationaryThrottled(mName, true, mOutgoingRequest);
             }
 
             if (mDeliverLastLocationCallback != null) {
@@ -224,7 +224,7 @@
             }
         } else {
             if (oldThrottlingIntervalMs != INTERVAL_DISABLED) {
-                EVENT_LOG.logProviderStationaryThrottled(mName, false);
+                EVENT_LOG.logProviderStationaryThrottled(mName, false, mOutgoingRequest);
                 if (D) {
                     Log.d(TAG, mName + " provider stationary unthrottled");
                 }
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index abcf4fb..b10d56b 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -384,7 +384,7 @@
         if (mPlaybackState == null) {
             return false;
         }
-        return mPlaybackState.isActive() == expected;
+        return mPlaybackState.isActiveState() == expected;
     }
 
     /**
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
index 8bd3b1e..42b7c9d3 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
@@ -100,7 +100,7 @@
 
         IntentFilter deletionFilter = new IntentFilter(ACTION_HISTORY_DELETION);
         deletionFilter.addDataScheme(SCHEME_DELETION);
-        mContext.registerReceiver(mFileCleaupReceiver, deletionFilter);
+        mContext.registerReceiver(mFileCleanupReceiver, deletionFilter);
     }
 
     public void init() {
@@ -273,13 +273,36 @@
         }
     }
 
+    /**
+     * Remove the first entry from the list of history files whose file matches the given file path.
+     *
+     * This method is necessary for anything that only has an absolute file path rather than an
+     * AtomicFile object from the list of history files.
+     *
+     * filePath should be an absolute path.
+     */
+    void removeFilePathFromHistory(String filePath) {
+        if (filePath == null) {
+            return;
+        }
+
+        Iterator<AtomicFile> historyFileItr = mHistoryFiles.iterator();
+        while (historyFileItr.hasNext()) {
+            final AtomicFile af = historyFileItr.next();
+            if (af != null && filePath.equals(af.getBaseFile().getAbsolutePath())) {
+                historyFileItr.remove();
+                return;
+            }
+        }
+    }
+
     private void deleteFile(AtomicFile file) {
         if (DEBUG) {
             Slog.d(TAG, "Removed " + file.getBaseFile().getName());
         }
         file.delete();
         // TODO: delete all relevant bitmaps, once they exist
-        mHistoryFiles.remove(file);
+        removeFilePathFromHistory(file.getBaseFile().getAbsolutePath());
     }
 
     private void scheduleDeletion(File file, long creationTime, int retentionDays) {
@@ -342,7 +365,7 @@
         }
     }
 
-    private final BroadcastReceiver mFileCleaupReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mFileCleanupReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
@@ -358,7 +381,7 @@
                             Slog.d(TAG, "Removed " + fileToDelete.getBaseFile().getName());
                         }
                         fileToDelete.delete();
-                        mHistoryFiles.remove(fileToDelete);
+                        removeFilePathFromHistory(filePath);
                     }
                 } catch (Exception e) {
                     Slog.e(TAG, "Failed to delete notification history file", e);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3ddfe6c..1e6e5cb 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -380,8 +380,6 @@
     static final int INVALID_UID = -1;
     static final String ROOT_PKG = "root";
 
-    static final boolean ENABLE_BLOCKED_TOASTS = true;
-
     static final String[] DEFAULT_ALLOWED_ADJUSTMENTS = new String[] {
             Adjustment.KEY_CONTEXTUAL_ACTIONS,
             Adjustment.KEY_TEXT_REPLIES,
@@ -3165,34 +3163,11 @@
             }
 
             final int callingUid = Binder.getCallingUid();
-            final UserHandle callingUser = Binder.getCallingUserHandle();
+            checkCallerIsSameApp(pkg);
             final boolean isSystemToast = isCallerSystemOrPhone()
                     || PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg);
-            final boolean isPackageSuspended = isPackagePaused(pkg);
-            final boolean notificationsDisabledForPackage = !areNotificationsEnabledForPackage(pkg,
-                    callingUid);
-
-            final boolean appIsForeground;
-            final long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                appIsForeground = mActivityManager.getUidImportance(callingUid)
-                        == IMPORTANCE_FOREGROUND;
-            } finally {
-                Binder.restoreCallingIdentity(callingIdentity);
-            }
-
-            if (ENABLE_BLOCKED_TOASTS && !isSystemToast && ((notificationsDisabledForPackage
-                    && !appIsForeground) || isPackageSuspended)) {
-                Slog.e(TAG, "Suppressing toast from package " + pkg
-                        + (isPackageSuspended ? " due to package suspended."
-                        : " by user request."));
-                return;
-            }
-
             boolean isAppRenderedToast = (callback != null);
-            if (blockToast(callingUid, isSystemToast, isAppRenderedToast)) {
-                Slog.w(TAG, "Blocking custom toast from package " + pkg
-                        + " due to package not in the foreground at time the toast was posted");
+            if (!checkCanEnqueueToast(pkg, callingUid, isAppRenderedToast, isSystemToast)) {
                 return;
             }
 
@@ -3246,6 +3221,39 @@
             }
         }
 
+        private boolean checkCanEnqueueToast(String pkg, int callingUid,
+                boolean isAppRenderedToast, boolean isSystemToast) {
+            final boolean isPackageSuspended = isPackagePaused(pkg);
+            final boolean notificationsDisabledForPackage = !areNotificationsEnabledForPackage(pkg,
+                    callingUid);
+
+            final boolean appIsForeground;
+            final long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                appIsForeground = mActivityManager.getUidImportance(callingUid)
+                        == IMPORTANCE_FOREGROUND;
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+
+            if (!isSystemToast && ((notificationsDisabledForPackage && !appIsForeground)
+                    || isPackageSuspended)) {
+                Slog.e(TAG, "Suppressing toast from package " + pkg
+                        + (isPackageSuspended ? " due to package suspended."
+                        : " by user request."));
+                return false;
+            }
+
+            if (blockToast(callingUid, isSystemToast, isAppRenderedToast,
+                    isPackageInForegroundForToast(callingUid))) {
+                Slog.w(TAG, "Blocking custom toast from package " + pkg
+                        + " due to package not in the foreground at time the toast was posted");
+                return false;
+            }
+
+            return true;
+        }
+
         @Override
         public void cancelToast(String pkg, IBinder token) {
             Slog.i(TAG, "cancelToast pkg=" + pkg + " token=" + token);
@@ -7692,12 +7700,13 @@
             boolean isWithinQuota =
                     mToastRateLimiter.isWithinQuota(userId, record.pkg, TOAST_QUOTA_TAG)
                             || isExemptFromRateLimiting(record.pkg, userId);
+            boolean isPackageInForeground = isPackageInForegroundForToast(record.uid);
 
             if (tryShowToast(
-                    record, rateLimitingEnabled, isWithinQuota)) {
+                    record, rateLimitingEnabled, isWithinQuota, isPackageInForeground)) {
                 scheduleDurationReachedLocked(record, lastToastWasTextRecord);
                 mIsCurrentToastShown = true;
-                if (rateLimitingEnabled) {
+                if (rateLimitingEnabled && !isPackageInForeground) {
                     mToastRateLimiter.noteEvent(userId, record.pkg, TOAST_QUOTA_TAG);
                 }
                 return;
@@ -7713,14 +7722,15 @@
 
     /** Returns true if it successfully showed the toast. */
     private boolean tryShowToast(ToastRecord record, boolean rateLimitingEnabled,
-            boolean isWithinQuota) {
-        if (rateLimitingEnabled && !isWithinQuota) {
+            boolean isWithinQuota, boolean isPackageInForeground) {
+        if (rateLimitingEnabled && !isWithinQuota && !isPackageInForeground) {
             reportCompatRateLimitingToastsChange(record.uid);
             Slog.w(TAG, "Package " + record.pkg + " is above allowed toast quota, the "
                     + "following toast was blocked and discarded: " + record);
             return false;
         }
-        if (blockToast(record.uid, record.isSystemToast, record.isAppRendered())) {
+        if (blockToast(record.uid, record.isSystemToast, record.isAppRendered(),
+                isPackageInForeground)) {
             Slog.w(TAG, "Blocking custom toast from package " + record.pkg
                     + " due to package not in the foreground at the time of showing the toast");
             return false;
@@ -7911,10 +7921,11 @@
      * with targetSdk < R. For apps with targetSdk R+, text toasts are not app-rendered, so
      * isAppRenderedToast == true means it's a custom toast.
      */
-    private boolean blockToast(int uid, boolean isSystemToast, boolean isAppRenderedToast) {
+    private boolean blockToast(int uid, boolean isSystemToast, boolean isAppRenderedToast,
+            boolean isPackageInForeground) {
         return isAppRenderedToast
                 && !isSystemToast
-                && !isPackageInForegroundForToast(uid)
+                && !isPackageInForeground
                 && CompatChanges.isChangeEnabled(CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK, uid);
     }
 
diff --git a/services/core/java/com/android/server/om/OverlayReferenceMapper.java b/services/core/java/com/android/server/om/OverlayReferenceMapper.java
index a9dbb2f..94a1f3b 100644
--- a/services/core/java/com/android/server/om/OverlayReferenceMapper.java
+++ b/services/core/java/com/android/server/om/OverlayReferenceMapper.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Pair;
 import android.util.Slog;
 
@@ -28,10 +30,10 @@
 import com.android.server.SystemConfig;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 
@@ -84,14 +86,15 @@
      * See class comment for specific types.
      */
     @GuardedBy("mLock")
-    private final Map<String, Map<String, Set<String>>> mActorToTargetToOverlays = new HashMap<>();
+    private final ArrayMap<String, ArrayMap<String, ArraySet<String>>> mActorToTargetToOverlays =
+            new ArrayMap<>();
 
     /**
      * Keys are actor package names, values are generic package names the actor should be able
      * to see.
      */
     @GuardedBy("mLock")
-    private final Map<String, Set<String>> mActorPkgToPkgs = new HashMap<>();
+    private final ArrayMap<String, Set<String>> mActorPkgToPkgs = new ArrayMap<>();
 
     @GuardedBy("mLock")
     private boolean mDeferRebuild;
@@ -160,21 +163,26 @@
      *
      * @param pkg the package to add
      * @param otherPkgs map of other packages to consider, excluding {@param pkg}
+     * @return Set of packages that may have changed visibility
      */
-    public void addPkg(AndroidPackage pkg, Map<String, AndroidPackage> otherPkgs) {
+    public ArraySet<String> addPkg(AndroidPackage pkg, Map<String, AndroidPackage> otherPkgs) {
         synchronized (mLock) {
+            ArraySet<String> changed = new ArraySet<>();
+
             if (!pkg.getOverlayables().isEmpty()) {
-                addTarget(pkg, otherPkgs);
+                addTarget(pkg, otherPkgs, changed);
             }
 
             // TODO(b/135203078): Replace with isOverlay boolean flag check; fix test mocks
             if (!mProvider.getTargetToOverlayables(pkg).isEmpty()) {
-                addOverlay(pkg, otherPkgs);
+                addOverlay(pkg, otherPkgs, changed);
             }
 
             if (!mDeferRebuild) {
                 rebuild();
             }
+
+            return changed;
         }
     }
 
@@ -184,27 +192,40 @@
      * of {@link SystemConfig#getNamedActors()}.
      *
      * @param pkgName name to remove, as was added through {@link #addPkg(AndroidPackage, Map)}
+     * @return Set of packages that may have changed visibility
      */
-    public void removePkg(String pkgName) {
+    public ArraySet<String> removePkg(String pkgName) {
         synchronized (mLock) {
-            removeTarget(pkgName);
-            removeOverlay(pkgName);
+            ArraySet<String> changedPackages = new ArraySet<>();
+            removeTarget(pkgName, changedPackages);
+            removeOverlay(pkgName, changedPackages);
 
             if (!mDeferRebuild) {
                 rebuild();
             }
+
+            return changedPackages;
         }
     }
 
-    private void removeTarget(String target) {
+    /**
+     * @param changedPackages Ongoing collection of packages that may have changed visibility
+     */
+    private void removeTarget(String target, @NonNull Collection<String> changedPackages) {
         synchronized (mLock) {
-            Iterator<Map<String, Set<String>>> iterator =
-                    mActorToTargetToOverlays.values().iterator();
-            while (iterator.hasNext()) {
-                Map<String, Set<String>> next = iterator.next();
-                next.remove(target);
-                if (next.isEmpty()) {
-                    iterator.remove();
+            int size = mActorToTargetToOverlays.size();
+            for (int index = size - 1; index >= 0; index--) {
+                ArrayMap<String, ArraySet<String>> targetToOverlays =
+                        mActorToTargetToOverlays.valueAt(index);
+                if (targetToOverlays.containsKey(target)) {
+                    targetToOverlays.remove(target);
+
+                    String actor = mActorToTargetToOverlays.keyAt(index);
+                    changedPackages.add(mProvider.getActorPkg(actor));
+
+                    if (targetToOverlays.isEmpty()) {
+                        mActorToTargetToOverlays.removeAt(index);
+                    }
                 }
             }
         }
@@ -215,16 +236,19 @@
      *
      * If a target overlays itself, it will not be associated with itself, as only one half of the
      * relationship needs to exist for visibility purposes.
+     *
+     * @param changedPackages Ongoing collection of packages that may have changed visibility
      */
-    private void addTarget(AndroidPackage targetPkg, Map<String, AndroidPackage> otherPkgs) {
+    private void addTarget(AndroidPackage targetPkg, Map<String, AndroidPackage> otherPkgs,
+            @NonNull Collection<String> changedPackages) {
         synchronized (mLock) {
             String target = targetPkg.getPackageName();
-            removeTarget(target);
+            removeTarget(target, changedPackages);
 
             Map<String, String> overlayablesToActors = targetPkg.getOverlayables();
             for (String overlayable : overlayablesToActors.keySet()) {
                 String actor = overlayablesToActors.get(overlayable);
-                addTargetToMap(actor, target);
+                addTargetToMap(actor, target, changedPackages);
 
                 for (AndroidPackage overlayPkg : otherPkgs.values()) {
                     Map<String, Set<String>> targetToOverlayables =
@@ -235,18 +259,38 @@
                     }
 
                     if (overlayables.contains(overlayable)) {
-                        addOverlayToMap(actor, target, overlayPkg.getPackageName());
+                        String overlay = overlayPkg.getPackageName();
+                        addOverlayToMap(actor, target, overlay, changedPackages);
                     }
                 }
             }
         }
     }
 
-    private void removeOverlay(String overlay) {
+    /**
+     * @param changedPackages Ongoing collection of packages that may have changed visibility
+     */
+    private void removeOverlay(String overlay, @NonNull Collection<String> changedPackages) {
         synchronized (mLock) {
-            for (Map<String, Set<String>> targetToOverlays : mActorToTargetToOverlays.values()) {
-                for (Set<String> overlays : targetToOverlays.values()) {
-                    overlays.remove(overlay);
+            int actorsSize = mActorToTargetToOverlays.size();
+            for (int actorIndex = actorsSize - 1; actorIndex >= 0; actorIndex--) {
+                ArrayMap<String, ArraySet<String>> targetToOverlays =
+                        mActorToTargetToOverlays.valueAt(actorIndex);
+                int targetsSize = targetToOverlays.size();
+                for (int targetIndex = targetsSize - 1; targetIndex >= 0; targetIndex--) {
+                    final Set<String> overlays = targetToOverlays.valueAt(targetIndex);
+
+                    if (overlays.remove(overlay)) {
+                        String actor = mActorToTargetToOverlays.keyAt(actorIndex);
+                        changedPackages.add(mProvider.getActorPkg(actor));
+
+                        // targetToOverlays should not be removed here even if empty as the actor
+                        // will still have visibility to the target even if no overlays exist
+                    }
+                }
+
+                if (targetToOverlays.isEmpty()) {
+                    mActorToTargetToOverlays.removeAt(actorIndex);
                 }
             }
         }
@@ -257,11 +301,14 @@
      *
      * If an overlay targets itself, it will not be associated with itself, as only one half of the
      * relationship needs to exist for visibility purposes.
+     *
+     * @param changedPackages Ongoing collection of packages that may have changed visibility
      */
-    private void addOverlay(AndroidPackage overlayPkg, Map<String, AndroidPackage> otherPkgs) {
+    private void addOverlay(AndroidPackage overlayPkg, Map<String, AndroidPackage> otherPkgs,
+            @NonNull Collection<String> changedPackages) {
         synchronized (mLock) {
             String overlay = overlayPkg.getPackageName();
-            removeOverlay(overlay);
+            removeOverlay(overlay, changedPackages);
 
             Map<String, Set<String>> targetToOverlayables =
                     mProvider.getTargetToOverlayables(overlayPkg);
@@ -280,7 +327,7 @@
                     if (TextUtils.isEmpty(actor)) {
                         continue;
                     }
-                    addOverlayToMap(actor, targetPkgName, overlay);
+                    addOverlayToMap(actor, targetPkgName, overlay, changedPackages);
                 }
             }
         }
@@ -312,7 +359,8 @@
                     continue;
                 }
 
-                Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
+                ArrayMap<String, ArraySet<String>> targetToOverlays =
+                        mActorToTargetToOverlays.get(actor);
                 Set<String> pkgs = new HashSet<>();
 
                 for (String target : targetToOverlays.keySet()) {
@@ -326,36 +374,51 @@
         }
     }
 
-    private void addTargetToMap(String actor, String target) {
-        Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
+    /**
+     * @param changedPackages Ongoing collection of packages that may have changed visibility
+     */
+    private void addTargetToMap(String actor, String target,
+            @NonNull Collection<String> changedPackages) {
+        ArrayMap<String, ArraySet<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
         if (targetToOverlays == null) {
-            targetToOverlays = new HashMap<>();
+            targetToOverlays = new ArrayMap<>();
             mActorToTargetToOverlays.put(actor, targetToOverlays);
         }
 
-        Set<String> overlays = targetToOverlays.get(target);
+        ArraySet<String> overlays = targetToOverlays.get(target);
         if (overlays == null) {
-            overlays = new HashSet<>();
+            overlays = new ArraySet<>();
             targetToOverlays.put(target, overlays);
         }
+
+        // For now, only actors themselves can gain or lose visibility through package changes
+        changedPackages.add(mProvider.getActorPkg(actor));
     }
 
-    private void addOverlayToMap(String actor, String target, String overlay) {
+    /**
+     * @param changedPackages Ongoing collection of packages that may have changed visibility
+     */
+    private void addOverlayToMap(String actor, String target, String overlay,
+            @NonNull Collection<String> changedPackages) {
         synchronized (mLock) {
-            Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
+            ArrayMap<String, ArraySet<String>> targetToOverlays =
+                    mActorToTargetToOverlays.get(actor);
             if (targetToOverlays == null) {
-                targetToOverlays = new HashMap<>();
+                targetToOverlays = new ArrayMap<>();
                 mActorToTargetToOverlays.put(actor, targetToOverlays);
             }
 
-            Set<String> overlays = targetToOverlays.get(target);
+            ArraySet<String> overlays = targetToOverlays.get(target);
             if (overlays == null) {
-                overlays = new HashSet<>();
+                overlays = new ArraySet<>();
                 targetToOverlays.put(target, overlays);
             }
 
             overlays.add(overlay);
         }
+
+        // For now, only actors themselves can gain or lose visibility through package changes
+        changedPackages.add(mProvider.getActorPkg(actor));
     }
 
     public interface Provider {
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index ca8202f..4f527f2 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -662,11 +662,27 @@
                 removePackage(newPkgSetting);
             }
             mStateProvider.runWithState((settings, users) -> {
-                addPackageInternal(newPkgSetting, settings);
+                ArraySet<String> additionalChangedPackages =
+                        addPackageInternal(newPkgSetting, settings);
                 synchronized (mCacheLock) {
                     if (mShouldFilterCache != null) {
                         updateShouldFilterCacheForPackage(mShouldFilterCache, null, newPkgSetting,
                                 settings, users, settings.size());
+                        if (additionalChangedPackages != null) {
+                            for (int index = 0; index < additionalChangedPackages.size(); index++) {
+                                String changedPackage = additionalChangedPackages.valueAt(index);
+                                PackageSetting changedPkgSetting = settings.get(changedPackage);
+                                if (changedPkgSetting == null) {
+                                    // It's possible for the overlay mapper to know that an actor
+                                    // package changed via an explicit reference, even if the actor
+                                    // isn't installed, so skip if that's the case.
+                                    continue;
+                                }
+
+                                updateShouldFilterCacheForPackage(mShouldFilterCache, null,
+                                        changedPkgSetting, settings, users, settings.size());
+                            }
+                        }
                     } // else, rebuild entire cache when system is ready
                 }
             });
@@ -676,7 +692,12 @@
         }
     }
 
-    private void addPackageInternal(PackageSetting newPkgSetting,
+    /**
+     * @return Additional packages that may have had their viewing visibility changed and may need
+     * to be updated in the cache. Returns null if there are no additional packages.
+     */
+    @Nullable
+    private ArraySet<String> addPackageInternal(PackageSetting newPkgSetting,
             ArrayMap<String, PackageSetting> existingSettings) {
         if (Objects.equals("android", newPkgSetting.name)) {
             // let's set aside the framework signatures
@@ -692,8 +713,7 @@
 
         final AndroidPackage newPkg = newPkgSetting.pkg;
         if (newPkg == null) {
-            // nothing to add
-            return;
+            return null;
         }
 
         if (mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts())) {
@@ -765,8 +785,13 @@
                 existingPkgs.put(pkgSetting.name, pkgSetting.pkg);
             }
         }
-        mOverlayReferenceMapper.addPkg(newPkgSetting.pkg, existingPkgs);
+
+        ArraySet<String> changedPackages =
+                mOverlayReferenceMapper.addPkg(newPkgSetting.pkg, existingPkgs);
+
         mFeatureConfig.updatePackageState(newPkgSetting, false /*removed*/);
+
+        return changedPackages;
     }
 
     @GuardedBy("mCacheLock")
@@ -1080,7 +1105,9 @@
                 }
             }
 
-            mOverlayReferenceMapper.removePkg(setting.name);
+            ArraySet<String> additionalChangedPackages =
+                    mOverlayReferenceMapper.removePkg(setting.name);
+
             mFeatureConfig.updatePackageState(setting, true /*removed*/);
 
             // After removing all traces of the package, if it's part of a shared user, re-add other
@@ -1109,6 +1136,25 @@
                                 siblingSetting, settings, users, settings.size());
                     }
                 }
+
+                if (mShouldFilterCache != null) {
+                    if (additionalChangedPackages != null) {
+                        for (int index = 0; index < additionalChangedPackages.size(); index++) {
+                            String changedPackage = additionalChangedPackages.valueAt(index);
+                            PackageSetting changedPkgSetting = settings.get(changedPackage);
+                            if (changedPkgSetting == null) {
+                                // It's possible for the overlay mapper to know that an actor
+                                // package changed via an explicit reference, even if the actor
+                                // isn't installed, so skip if that's the case.
+                                continue;
+                            }
+
+                            updateShouldFilterCacheForPackage(mShouldFilterCache, null,
+                                    changedPkgSetting, settings, users, settings.size());
+                        }
+                    }
+                }
+
                 onChanged();
             }
         });
diff --git a/services/core/java/com/android/server/pm/DumpState.java b/services/core/java/com/android/server/pm/DumpState.java
index 6875b8a..ec79483 100644
--- a/services/core/java/com/android/server/pm/DumpState.java
+++ b/services/core/java/com/android/server/pm/DumpState.java
@@ -45,6 +45,7 @@
     public static final int DUMP_QUERIES = 1 << 26;
     public static final int DUMP_KNOWN_PACKAGES = 1 << 27;
     public static final int DUMP_PER_UID_READ_TIMEOUTS = 1 << 28;
+    public static final int DUMP_SNAPSHOT_STATISTICS = 1 << 29;
 
     public static final int OPTION_SHOW_FILTERS = 1 << 0;
     public static final int OPTION_DUMP_ALL_COMPONENTS = 1 << 1;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 85c5a5e..59039ba 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1914,6 +1914,18 @@
      */
     private interface Computer {
 
+        /**
+         * Administrative statistics: record that the snapshot has been used.  Every call
+         * to use() increments the usage counter.
+         */
+        void use();
+
+        /**
+         * Fetch the snapshot usage counter.
+         * @return The number of times this snapshot was used.
+         */
+        int getUsed();
+
         @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType,
                 int flags, @PrivateResolveFlags int privateResolveFlags, int filterCallingUid,
                 int userId, boolean resolveForStart, boolean allowDynamicSplits);
@@ -2065,6 +2077,9 @@
      */
     private static class ComputerEngine implements Computer {
 
+        // The administrative use counter.
+        private int mUsed = 0;
+
         // Cached attributes.  The names in this class are the same as the
         // names in PackageManagerService; see that class for documentation.
         protected final Settings mSettings;
@@ -2157,6 +2172,20 @@
             mService = args.service;
         }
 
+        /**
+         * Record that the snapshot was used.
+         */
+        public void use() {
+            mUsed++;
+        }
+
+        /**
+         * Return the usage counter.
+         */
+        public int getUsed() {
+            return mUsed;
+        }
+
         public @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
                 String resolvedType, int flags, @PrivateResolveFlags int privateResolveFlags,
                 int filterCallingUid, int userId, boolean resolveForStart,
@@ -4885,121 +4914,11 @@
      */
     private final Object mSnapshotLock = new Object();
 
-    // A counter of all queries that hit the current snapshot.
-    @GuardedBy("mSnapshotLock")
-    private int mSnapshotHits = 0;
-
-    // A class to record snapshot statistics.
-    private static class SnapshotStatistics {
-        // A build time is "big" if it takes longer than 5ms.
-        private static final long SNAPSHOT_BIG_BUILD_TIME_NS = TimeUnit.MILLISECONDS.toNanos(5);
-
-        // A snapshot is in quick succession to the previous snapshot if it less than
-        // 100ms since the previous snapshot.
-        private static final long SNAPSHOT_QUICK_REBUILD_INTERVAL_NS =
-                TimeUnit.MILLISECONDS.toNanos(100);
-
-        // The interval between snapshot statistics logging, in ns.
-        private static final long SNAPSHOT_LOG_INTERVAL_NS = TimeUnit.MINUTES.toNanos(10);
-
-        // The throttle parameters for big build reporting.  Do not report more than this
-        // many events in a single log interval.
-        private static final int SNAPSHOT_BUILD_REPORT_LIMIT = 10;
-
-        // The time the snapshot statistics were last logged.
-        private long mStatisticsSent = 0;
-
-        // The number of build events logged since the last periodic log.
-        private int mLoggedBuilds = 0;
-
-        // The time of the last build.
-        private long mLastBuildTime = 0;
-
-        // The number of times the snapshot has been rebuilt since the statistics were
-        // last logged.
-        private int mRebuilds = 0;
-
-        // The number of times the snapshot has been used since it was rebuilt.
-        private int mReused = 0;
-
-        // The number of "big" build times since the last log.  "Big" is defined by
-        // SNAPSHOT_BIG_BUILD_TIME.
-        private int mBigBuilds = 0;
-
-        // The number of quick rebuilds.  "Quick" is defined by
-        // SNAPSHOT_QUICK_REBUILD_INTERVAL_NS.
-        private int mQuickRebuilds = 0;
-
-        // The time take to build a snapshot.  This is cumulative over the rebuilds recorded
-        // in mRebuilds, so the average time to build a snapshot is given by
-        // mBuildTimeNs/mRebuilds.
-        private int mBuildTimeNs = 0;
-
-        // The maximum build time since the last log.
-        private long mMaxBuildTimeNs = 0;
-
-        // The constant that converts ns to ms.  This is the divisor.
-        private final long NS_TO_MS = TimeUnit.MILLISECONDS.toNanos(1);
-
-        // Convert ns to an int ms.  The maximum range of this method is about 24 days.
-        // There is no expectation that an event will take longer than that.
-        private int nsToMs(long ns) {
-            return (int) (ns / NS_TO_MS);
-        }
-
-        // The single method records a rebuild.  The "now" parameter is passed in because
-        // the caller needed it to computer the duration, so pass it in to avoid
-        // recomputing it.
-        private void rebuild(long now, long done, int hits) {
-            if (mStatisticsSent == 0) {
-                mStatisticsSent = now;
-            }
-            final long elapsed = now - mLastBuildTime;
-            final long duration = done - now;
-            mLastBuildTime = now;
-
-            if (mMaxBuildTimeNs < duration) {
-                mMaxBuildTimeNs = duration;
-            }
-            mRebuilds++;
-            mReused += hits;
-            mBuildTimeNs += duration;
-
-            boolean log_build = false;
-            if (duration > SNAPSHOT_BIG_BUILD_TIME_NS) {
-                log_build = true;
-                mBigBuilds++;
-            }
-            if (elapsed < SNAPSHOT_QUICK_REBUILD_INTERVAL_NS) {
-                log_build = true;
-                mQuickRebuilds++;
-            }
-            if (log_build && mLoggedBuilds < SNAPSHOT_BUILD_REPORT_LIMIT) {
-                EventLogTags.writePmSnapshotRebuild(nsToMs(duration), nsToMs(elapsed));
-                mLoggedBuilds++;
-            }
-
-            final long log_interval = now - mStatisticsSent;
-            if (log_interval >= SNAPSHOT_LOG_INTERVAL_NS) {
-                EventLogTags.writePmSnapshotStats(mRebuilds, mReused,
-                                                  mBigBuilds, mQuickRebuilds,
-                                                  nsToMs(mMaxBuildTimeNs),
-                                                  nsToMs(mBuildTimeNs));
-                mStatisticsSent = now;
-                mRebuilds = 0;
-                mReused = 0;
-                mBuildTimeNs = 0;
-                mMaxBuildTimeNs = 0;
-                mBigBuilds = 0;
-                mQuickRebuilds = 0;
-                mLoggedBuilds = 0;
-            }
-        }
-    }
-
-    // Snapshot statistics.
-    @GuardedBy("mLock")
-    private final SnapshotStatistics mSnapshotStatistics = new SnapshotStatistics();
+    /**
+     * The snapshot statistics.  These are collected to track performance and to identify
+     * situations in which the snapshots are misbehaving.
+     */
+    private final SnapshotStatistics mSnapshotStatistics;
 
     // The snapshot disable/enable switch.  An image with the flag set true uses snapshots
     // and an image with the flag set false does not use snapshots.
@@ -5033,10 +4952,9 @@
             Computer c = mSnapshotComputer;
             if (sSnapshotCorked && (c != null)) {
                 // Snapshots are corked, which means new ones should not be built right now.
+                c.use();
                 return c;
             }
-            // Deliberately capture the value pre-increment
-            final int hits = mSnapshotHits++;
             if (sSnapshotInvalid || (c == null)) {
                 // The snapshot is invalid if it is marked as invalid or if it is null.  If it
                 // is null, then it is currently being rebuilt by rebuildSnapshot().
@@ -5046,7 +4964,7 @@
                     // self-consistent (the lock is being held) and is current as of the time
                     // this function is entered.
                     if (sSnapshotInvalid) {
-                        rebuildSnapshot(hits);
+                        rebuildSnapshot();
                     }
 
                     // Guaranteed to be non-null.  mSnapshotComputer is only be set to null
@@ -5056,6 +4974,7 @@
                     c = mSnapshotComputer;
                 }
             }
+            c.use();
             return c;
         }
     }
@@ -5065,16 +4984,16 @@
      * threads from using the invalid computer until it is rebuilt.
      */
     @GuardedBy("mLock")
-    private void rebuildSnapshot(int hits) {
-        final long now = System.nanoTime();
+    private void rebuildSnapshot() {
+        final long now = SystemClock.currentTimeMicro();
+        final int hits = mSnapshotComputer == null ? -1 : mSnapshotComputer.getUsed();
         mSnapshotComputer = null;
         sSnapshotInvalid = false;
         final Snapshot args = new Snapshot(Snapshot.SNAPPED);
         mSnapshotComputer = new ComputerEngine(args);
-        final long done = System.nanoTime();
+        final long done = SystemClock.currentTimeMicro();
 
         mSnapshotStatistics.rebuild(now, done, hits);
-        mSnapshotHits = 0;
     }
 
     /**
@@ -6327,6 +6246,7 @@
         mSnapshotEnabled = false;
         mLiveComputer = createLiveComputer();
         mSnapshotComputer = null;
+        mSnapshotStatistics = null;
 
         mPackages.putAll(testParams.packages);
         mEnableFreeCacheV2 = testParams.enableFreeCacheV2;
@@ -6479,17 +6399,20 @@
         mDomainVerificationManager = injector.getDomainVerificationManagerInternal();
         mDomainVerificationManager.setConnection(mDomainVerificationConnection);
 
-        // Create the computer as soon as the state objects have been installed.  The
-        // cached computer is the same as the live computer until the end of the
-        // constructor, at which time the invalidation method updates it.  The cache is
-        // corked initially to ensure a cached computer is not built until the end of the
-        // constructor.
-        mSnapshotEnabled = SNAPSHOT_ENABLED;
-        sSnapshotCorked = true;
-        sSnapshotInvalid = true;
-        mLiveComputer = createLiveComputer();
-        mSnapshotComputer = null;
-        registerObserver();
+        synchronized (mLock) {
+            // Create the computer as soon as the state objects have been installed.  The
+            // cached computer is the same as the live computer until the end of the
+            // constructor, at which time the invalidation method updates it.  The cache is
+            // corked initially to ensure a cached computer is not built until the end of the
+            // constructor.
+            mSnapshotEnabled = SNAPSHOT_ENABLED;
+            sSnapshotCorked = true;
+            sSnapshotInvalid = true;
+            mSnapshotStatistics = new SnapshotStatistics();
+            mLiveComputer = createLiveComputer();
+            mSnapshotComputer = null;
+            registerObserver();
+        }
 
         // CHECKSTYLE:OFF IndentationCheck
         synchronized (mInstallLock) {
@@ -23957,6 +23880,7 @@
                 pw.println("    dexopt: dump dexopt state");
                 pw.println("    compiler-stats: dump compiler statistics");
                 pw.println("    service-permissions: dump permissions required by services");
+                pw.println("    snapshot: dump snapshot statistics");
                 pw.println("    known-packages: dump known packages");
                 pw.println("    <package.name>: info about given package");
                 return;
@@ -24105,6 +24029,8 @@
                 dumpState.setDump(DumpState.DUMP_KNOWN_PACKAGES);
             } else if ("t".equals(cmd) || "timeouts".equals(cmd)) {
                 dumpState.setDump(DumpState.DUMP_PER_UID_READ_TIMEOUTS);
+            } else if ("snapshot".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_SNAPSHOT_STATISTICS);
             } else if ("write".equals(cmd)) {
                 synchronized (mLock) {
                     writeSettingsLPrTEMP();
@@ -24433,6 +24359,22 @@
                 pw.println(")");
             }
         }
+
+        if (!checkin && dumpState.isDumping(DumpState.DUMP_SNAPSHOT_STATISTICS)) {
+            pw.println("Snapshot statistics");
+            if (!mSnapshotEnabled) {
+                pw.println("  Snapshots disabled");
+            } else {
+                int hits = 0;
+                synchronized (mSnapshotLock) {
+                    if (mSnapshotComputer != null) {
+                        hits = mSnapshotComputer.getUsed();
+                    }
+                }
+                final long now = SystemClock.currentTimeMicro();
+                mSnapshotStatistics.dump(pw, "  ", now, hits, true);
+            }
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/SnapshotStatistics.java b/services/core/java/com/android/server/pm/SnapshotStatistics.java
new file mode 100644
index 0000000..c425bad5
--- /dev/null
+++ b/services/core/java/com/android/server/pm/SnapshotStatistics.java
@@ -0,0 +1,622 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.text.TextUtils;
+
+import com.android.server.EventLogTags;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * This class records statistics about PackageManagerService snapshots.  It maintains two sets of
+ * statistics: a periodic set which represents the last 10 minutes, and a cumulative set since
+ * process boot.  The key metrics that are recorded are:
+ * <ul>
+ * <li> The time to create a snapshot - this is the performance cost of a snapshot
+ * <li> The lifetime of the snapshot - creation time over lifetime is the amortized cost
+ * <li> The number of times a snapshot is reused - this is the number of times lock
+ *      contention was avoided.
+ * </ul>
+
+ * The time conversions in this class are designed to keep arithmetic using ints, rather
+ * than longs.  Raw times are supplied as longs in units of us.  These are left long.
+ * Rebuild durations however, are converted to ints.  An int can express a duration of
+ * approximately 35 minutes.  This is longer than any expected snapshot rebuild time, so
+ * an int is satisfactory.  The exception is the cumulative rebuild time over the course
+ * of a monitoring cycle: this value is kept long since the cycle time is one week and in
+ * a badly behaved system, the rebuild time might exceed 35 minutes.
+
+ * @hide
+ */
+public class SnapshotStatistics {
+    /**
+     * The interval at which statistics should be ticked.  It is 60s.  The interval is in
+     * units of milliseconds because that is what's required by Handler.sendMessageDelayed().
+     */
+    public static final int SNAPSHOT_TICK_INTERVAL_MS = 60 * 1000;
+
+    /**
+     * The number of ticks for long statistics.  This is one week.
+     */
+    public static final int SNAPSHOT_LONG_TICKS = 7 * 24 * 60;
+
+    /**
+     * The number snapshot event logs that can be generated in a single logging interval.
+     * A small number limits the logging generated by this class.  A snapshot event log is
+     * generated for every big snapshot build time, up to the limit, or whenever the
+     * maximum build time is exceeded in the logging interval.
+     */
+    public static final int SNAPSHOT_BUILD_REPORT_LIMIT = 10;
+
+    /**
+     * The number of microseconds in a millisecond.
+     */
+    private static final int US_IN_MS = 1000;
+
+    /**
+     * A snapshot build time is "big" if it takes longer than 10ms.
+     */
+    public static final int SNAPSHOT_BIG_BUILD_TIME_US = 10 * US_IN_MS;
+
+    /**
+     * A snapshot build time is reportable if it takes longer than 30ms.  Testing shows
+     * that this is very rare.
+     */
+    public static final int SNAPSHOT_REPORTABLE_BUILD_TIME_US = 30 * US_IN_MS;
+
+    /**
+     * A snapshot is short-lived it used fewer than 5 times.
+     */
+    public static final int SNAPSHOT_SHORT_LIFETIME = 5;
+
+    /**
+     * The lock to control access to this object.
+     */
+    private final Object mLock = new Object();
+
+    /**
+     * The bins for the build time histogram.  Values are in us.
+     */
+    private final BinMap mTimeBins;
+
+    /**
+     * The bins for the snapshot use histogram.
+     */
+    private final BinMap mUseBins;
+
+    /**
+     * The number of events reported in the current tick.
+     */
+    private int mEventsReported = 0;
+
+    /**
+     * The tick counter.  At the default tick interval, this wraps every 4000 years or so.
+     */
+    private int mTicks = 0;
+
+    /**
+     * The handler used for the periodic ticks.
+     */
+    private Handler mHandler = null;
+
+    /**
+     * Convert ns to an int ms.  The maximum range of this method is about 24 days.  There
+     * is no expectation that an event will take longer than that.
+     */
+    private int usToMs(int us) {
+        return us / US_IN_MS;
+    }
+
+    /**
+     * This class exists to provide a fast bin lookup for histograms.  An instance has an
+     * integer array that maps incoming values to bins.  Values larger than the array are
+     * mapped to the top-most bin.
+     */
+    private static class BinMap {
+
+        // The number of bins
+        private int mCount;
+        // The mapping of low integers to bins
+        private int[] mBinMap;
+        // The maximum mapped value.  Values at or above this are mapped to the
+        // top bin.
+        private int mMaxBin;
+        // A copy of the original key
+        private int[] mUserKey;
+
+        /**
+         * Create a bin map.  The input is an array of integers, which must be
+         * monotonically increasing (this is not checked).  The result is an integer array
+         * as long as the largest value in the input.
+         */
+        BinMap(int[] userKey) {
+            mUserKey = Arrays.copyOf(userKey, userKey.length);
+            // The number of bins is the length of the keys, plus 1 (for the max).
+            mCount = mUserKey.length + 1;
+            // The maximum value is one more than the last one in the map.
+            mMaxBin = mUserKey[mUserKey.length - 1] + 1;
+            mBinMap = new int[mMaxBin + 1];
+
+            int j = 0;
+            for (int i = 0; i < mUserKey.length; i++) {
+                while (j <= mUserKey[i]) {
+                    mBinMap[j] = i;
+                    j++;
+                }
+            }
+            mBinMap[mMaxBin] = mUserKey.length;
+        }
+
+        /**
+         * Map a value to a bin.
+         */
+        public int getBin(int x) {
+            if (x >= 0 && x < mMaxBin) {
+                return mBinMap[x];
+            } else if (x >= mMaxBin) {
+                return mBinMap[mMaxBin];
+            } else {
+                // x is negative.  The bin will not be used.
+                return 0;
+            }
+        }
+
+        /**
+         * The number of bins in this map
+         */
+        public int count() {
+            return mCount;
+        }
+
+        /**
+         * For convenience, return the user key.
+         */
+        public int[] userKeys() {
+            return mUserKey;
+        }
+    }
+
+    /**
+     * A complete set of statistics.  These are public, making it simpler for a client to
+     * fetch the individual fields.
+     */
+    public class Stats {
+
+        /**
+         * The start time for this set of statistics, in us.
+         */
+        public long mStartTimeUs = 0;
+
+        /**
+         * The completion time for this set of statistics, in ns.  A value of zero means
+         * the statistics are still active.
+         */
+        public long mStopTimeUs = 0;
+
+        /**
+         * The build-time histogram.  The total number of rebuilds is the sum over the
+         * histogram entries.
+         */
+        public int[] mTimes;
+
+        /**
+         * The reuse histogram.  The total number of snapshot uses is the sum over the
+         * histogram entries.
+         */
+        public int[] mUsed;
+
+        /**
+         * The total number of rebuilds.  This could be computed by summing over the use
+         * bins, but is maintained separately for convenience.
+         */
+        public int mTotalBuilds = 0;
+
+        /**
+         * The total number of times any snapshot was used.
+         */
+        public int mTotalUsed = 0;
+
+        /**
+         * The total number of builds that count as big, which means they took longer than
+         * SNAPSHOT_BIG_BUILD_TIME_NS.
+         */
+        public int mBigBuilds = 0;
+
+        /**
+         * The total number of short-lived snapshots
+         */
+        public int mShortLived = 0;
+
+        /**
+         * The time taken to build snapshots.  This is cumulative over the rebuilds
+         * recorded in mRebuilds, so the average time to build a snapshot is given by
+         * mBuildTimeNs/mRebuilds.  Note that this cannot be computed from the histogram.
+         */
+        public long mTotalTimeUs = 0;
+
+        /**
+         * The maximum build time since the last log.
+         */
+        public int mMaxBuildTimeUs = 0;
+
+        /**
+         * Record the rebuild.  The parameters are the length of time it took to build the
+         * latest snapshot, and the number of times the _previous_ snapshot was used.  A
+         * negative value for used signals an invalid value, which is the case the first
+         * time a snapshot is every built.
+         */
+        private void rebuild(int duration, int used,
+                int buildBin, int useBin, boolean big, boolean quick) {
+            mTotalBuilds++;
+            mTimes[buildBin]++;
+
+            if (used >= 0) {
+                mTotalUsed += used;
+                mUsed[useBin]++;
+            }
+
+            mTotalTimeUs += duration;
+            boolean reportIt = false;
+
+            if (big) {
+                mBigBuilds++;
+            }
+            if (quick) {
+                mShortLived++;
+            }
+            if (mMaxBuildTimeUs < duration) {
+                mMaxBuildTimeUs = duration;
+            }
+        }
+
+        private Stats(long now) {
+            mStartTimeUs = now;
+            mTimes = new int[mTimeBins.count()];
+            mUsed = new int[mUseBins.count()];
+        }
+
+        /**
+         * Create a copy of the argument.  The copy is made under lock but can then be
+         * used without holding the lock.
+         */
+        private Stats(Stats orig) {
+            mStartTimeUs = orig.mStartTimeUs;
+            mStopTimeUs = orig.mStopTimeUs;
+            mTimes = Arrays.copyOf(orig.mTimes, orig.mTimes.length);
+            mUsed = Arrays.copyOf(orig.mUsed, orig.mUsed.length);
+            mTotalBuilds = orig.mTotalBuilds;
+            mTotalUsed = orig.mTotalUsed;
+            mBigBuilds = orig.mBigBuilds;
+            mShortLived = orig.mShortLived;
+            mTotalTimeUs = orig.mTotalTimeUs;
+            mMaxBuildTimeUs = orig.mMaxBuildTimeUs;
+        }
+
+        /**
+         * Set the end time for the statistics.  The end time is used only for reporting
+         * in the dump() method.
+         */
+        private void complete(long stop) {
+            mStopTimeUs = stop;
+        }
+
+        /**
+         * Format a time span into ddd:HH:MM:SS.  The input is in us.
+         */
+        private String durationToString(long us) {
+            // s has a range of several years
+            int s = (int) (us / (1000 * 1000));
+            int m = s / 60;
+            s %= 60;
+            int h = m / 60;
+            m %= 60;
+            int d = h / 24;
+            h %= 24;
+            if (d != 0) {
+                return TextUtils.formatSimple("%2d:%02d:%02d:%02d", d, h, m, s);
+            } else if (h != 0) {
+                return TextUtils.formatSimple("%2s %02d:%02d:%02d", "", h, m, s);
+            } else {
+                return TextUtils.formatSimple("%2s %2s %2d:%02d", "", "", m, s);
+            }
+        }
+
+        /**
+         * Print the prefix for dumping.  This does not generate a line to the output.
+         */
+        private void dumpPrefix(PrintWriter pw, String indent, long now, boolean header,
+                                String title) {
+            pw.print(indent + " ");
+            if (header) {
+                pw.format(Locale.US, "%-23s", title);
+            } else {
+                pw.format(Locale.US, "%11s", durationToString(now - mStartTimeUs));
+                if (mStopTimeUs != 0) {
+                    pw.format(Locale.US, " %11s", durationToString(now - mStopTimeUs));
+                } else {
+                    pw.format(Locale.US, " %11s", "now");
+                }
+            }
+        }
+
+        /**
+         * Dump the summary statistics record.  Choose the header or the data.
+         *    number of builds
+         *    number of uses
+         *    number of big builds
+         *    number of short lifetimes
+         *    cumulative build time, in seconds
+         *    maximum build time, in ms
+         */
+        private void dumpStats(PrintWriter pw, String indent, long now, boolean header) {
+            dumpPrefix(pw, indent, now, header, "Summary stats");
+            if (header) {
+                pw.format(Locale.US, "  %10s  %10s  %10s  %10s  %10s  %10s",
+                          "TotBlds", "TotUsed", "BigBlds", "ShortLvd",
+                          "TotTime", "MaxTime");
+            } else {
+                pw.format(Locale.US,
+                        "  %10d  %10d  %10d  %10d  %10d  %10d",
+                        mTotalBuilds, mTotalUsed, mBigBuilds, mShortLived,
+                        mTotalTimeUs / 1000, mMaxBuildTimeUs / 1000);
+            }
+            pw.println();
+        }
+
+        /**
+         * Dump the build time histogram.  Choose the header or the data.
+         */
+        private void dumpTimes(PrintWriter pw, String indent, long now, boolean header) {
+            dumpPrefix(pw, indent, now, header, "Build times");
+            if (header) {
+                int[] keys = mTimeBins.userKeys();
+                for (int i = 0; i < keys.length; i++) {
+                    pw.format(Locale.US, "  %10s",
+                            TextUtils.formatSimple("<= %dms", keys[i]));
+                }
+                pw.format(Locale.US, "  %10s",
+                        TextUtils.formatSimple("> %dms", keys[keys.length - 1]));
+            } else {
+                for (int i = 0; i < mTimes.length; i++) {
+                    pw.format(Locale.US, "  %10d", mTimes[i]);
+                }
+            }
+            pw.println();
+        }
+
+        /**
+         * Dump the usage histogram.  Choose the header or the data.
+         */
+        private void dumpUsage(PrintWriter pw, String indent, long now, boolean header) {
+            dumpPrefix(pw, indent, now, header, "Use counters");
+            if (header) {
+                int[] keys = mUseBins.userKeys();
+                for (int i = 0; i < keys.length; i++) {
+                    pw.format(Locale.US, "  %10s", TextUtils.formatSimple("<= %d", keys[i]));
+                }
+                pw.format(Locale.US, "  %10s",
+                        TextUtils.formatSimple("> %d", keys[keys.length - 1]));
+            } else {
+                for (int i = 0; i < mUsed.length; i++) {
+                    pw.format(Locale.US, "  %10d", mUsed[i]);
+                }
+            }
+            pw.println();
+        }
+
+        /**
+         * Dump something, based on the "what" parameter.
+         */
+        private void dump(PrintWriter pw, String indent, long now, boolean header, String what) {
+            if (what.equals("stats")) {
+                dumpStats(pw, indent, now, header);
+            } else if (what.equals("times")) {
+                dumpTimes(pw, indent, now, header);
+            } else if (what.equals("usage")) {
+                dumpUsage(pw, indent, now, header);
+            } else {
+                throw new IllegalArgumentException("unrecognized choice: " + what);
+            }
+        }
+
+        /**
+         * Report the object via an event.  Presumably the record indicates an anomalous
+         * incident.
+         */
+        private void report() {
+            EventLogTags.writePmSnapshotStats(
+                    mTotalBuilds, mTotalUsed, mBigBuilds, mShortLived,
+                    mMaxBuildTimeUs / US_IN_MS, mTotalTimeUs / US_IN_MS);
+        }
+    }
+
+    /**
+     * Long statistics.  These roll over approximately every week.
+     */
+    private Stats[] mLong;
+
+    /**
+     * Short statistics.  These roll over approximately every minute;
+     */
+    private Stats[] mShort;
+
+    /**
+     * The time of the last build.  This can be used to compute the length of time a
+     * snapshot existed before being replaced.
+     */
+    private long mLastBuildTime = 0;
+
+    /**
+     * Create a snapshot object.  Initialize the bin levels.  The last bin catches
+     * everything that is not caught earlier, so its value is not really important.
+     */
+    public SnapshotStatistics() {
+        // Create the bin thresholds.  The time bins are in units of us.
+        mTimeBins = new BinMap(new int[] { 1, 2, 5, 10, 20, 50, 100 });
+        mUseBins = new BinMap(new int[] { 1, 2, 5, 10, 20, 50, 100 });
+
+        // Create the raw statistics
+        final long now = SystemClock.currentTimeMicro();
+        mLong = new Stats[2];
+        mLong[0] = new Stats(now);
+        mShort = new Stats[10];
+        mShort[0] = new Stats(now);
+
+        // Create the message handler for ticks and start the ticker.
+        mHandler = new Handler(Looper.getMainLooper()) {
+                @Override
+                public void handleMessage(Message msg) {
+                    SnapshotStatistics.this.handleMessage(msg);
+                }
+            };
+        scheduleTick();
+    }
+
+    /**
+     * Handle a message.  The only messages are ticks, so the message parameter is ignored.
+     */
+    private void handleMessage(@Nullable Message msg) {
+        tick();
+        scheduleTick();
+    }
+
+    /**
+     * Schedule one tick, a tick interval in the future.
+     */
+    private void scheduleTick() {
+        mHandler.sendEmptyMessageDelayed(0, SNAPSHOT_TICK_INTERVAL_MS);
+    }
+
+    /**
+     * Record a rebuild.  Cumulative and current statistics are updated.  Events may be
+     * generated.
+     * @param now The time at which the snapshot rebuild began, in ns.
+     * @param done The time at which the snapshot rebuild completed, in ns.
+     * @param hits The number of times the previous snapshot was used.
+     */
+    public void rebuild(long now, long done, int hits) {
+        // The duration has a span of about 2000s
+        final int duration = (int) (done - now);
+        boolean reportEvent = false;
+        synchronized (mLock) {
+            mLastBuildTime = now;
+
+            final int timeBin = mTimeBins.getBin(duration / 1000);
+            final int useBin = mUseBins.getBin(hits);
+            final boolean big = duration >= SNAPSHOT_BIG_BUILD_TIME_US;
+            final boolean quick = hits <= SNAPSHOT_SHORT_LIFETIME;
+
+            mShort[0].rebuild(duration, hits, timeBin, useBin, big, quick);
+            mLong[0].rebuild(duration, hits, timeBin, useBin, big, quick);
+            if (duration >= SNAPSHOT_REPORTABLE_BUILD_TIME_US) {
+                if (mEventsReported++ < SNAPSHOT_BUILD_REPORT_LIMIT) {
+                    reportEvent = true;
+                }
+            }
+        }
+        // The IO to the logger is done outside the lock.
+        if (reportEvent) {
+            // Report the first N big builds, and every new maximum after that.
+            EventLogTags.writePmSnapshotRebuild(duration / US_IN_MS, hits);
+        }
+    }
+
+    /**
+     * Roll a stats array.  Shift the elements up an index and create a new element at
+     * index zero.  The old element zero is completed with the specified time.
+     */
+    private void shift(Stats[] s, long now) {
+        s[0].complete(now);
+        for (int i = s.length - 1; i > 0; i--) {
+            s[i] = s[i - 1];
+        }
+        s[0] = new Stats(now);
+    }
+
+    /**
+     * Roll the statistics.
+     * <ul>
+     * <li> Roll the quick statistics immediately.
+     * <li> Roll the long statistics every SNAPSHOT_LONG_TICKER ticks.  The long
+     * statistics hold a week's worth of data.
+     * <li> Roll the logging statistics every SNAPSHOT_LOGGING_TICKER ticks.  The logging
+     * statistics hold 10 minutes worth of data.
+     * </ul>
+     */
+    private void tick() {
+        synchronized (mLock) {
+            long now = SystemClock.currentTimeMicro();
+            mTicks++;
+            if (mTicks % SNAPSHOT_LONG_TICKS == 0) {
+                shift(mLong, now);
+            }
+            shift(mShort, now);
+            mEventsReported = 0;
+        }
+    }
+
+    /**
+     * Dump the statistics.  The header is dumped from l[0], so that must not be null.
+     */
+    private void dump(PrintWriter pw, String indent, long now, Stats[] l, Stats[] s, String what) {
+        l[0].dump(pw, indent, now, true, what);
+        for (int i = 0; i < s.length; i++) {
+            if (s[i] != null) {
+                s[i].dump(pw, indent, now, false, what);
+            }
+        }
+        for (int i = 0; i < l.length; i++) {
+            if (l[i] != null) {
+                l[i].dump(pw, indent, now, false, what);
+            }
+        }
+    }
+
+    /**
+     * Dump the statistics.  The format is compatible with the PackageManager dumpsys
+     * output.
+     */
+    public void dump(PrintWriter pw, String indent, long now, int unrecorded, boolean full) {
+        // Grab the raw statistics under lock, but print them outside of the lock.
+        Stats[] l;
+        Stats[] s;
+        synchronized (mLock) {
+            l = Arrays.copyOf(mLong, mLong.length);
+            l[0] = new Stats(l[0]);
+            s = Arrays.copyOf(mShort, mShort.length);
+            s[0] = new Stats(s[0]);
+        }
+        pw.format(Locale.US, "%s Unrecorded hits %d", indent, unrecorded);
+        pw.println();
+        dump(pw, indent, now, l, s, "stats");
+        if (!full) {
+            return;
+        }
+        pw.println();
+        dump(pw, indent, now, l, s, "times");
+        pw.println();
+        dump(pw, indent, now, l, s, "usage");
+    }
+}
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index e913829..81cfbf7 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -85,6 +85,15 @@
           "include-annotation": "android.platform.test.annotations.Presubmit"
         }
       ]
+    },
+    {
+      "name": "PackageManagerServiceHostTests",
+      "file_patterns": ["AppsFilter\\.java"],
+      "options": [
+        {
+          "include-filter": "com.android.server.pm.test.OverlayActorVisibilityTest"
+        }
+      ]
     }
   ],
   "postsubmit": [
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
index bf2b3c7..a8a6a72 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
@@ -24,6 +24,7 @@
 import android.content.pm.parsing.component.ParsedActivity;
 import android.content.pm.parsing.component.ParsedIntentInfo;
 import android.os.Build;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Patterns;
 
@@ -32,7 +33,6 @@
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.util.List;
-import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -251,6 +251,10 @@
      * improve the reliability of any legacy verifiers.
      */
     private boolean isValidHost(String host) {
+        if (TextUtils.isEmpty(host)) {
+            return false;
+        }
+
         mDomainMatcher.reset(host);
         return mDomainMatcher.matches();
     }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
index 44ff3eb..246810f 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
@@ -22,6 +22,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.text.TextUtils;
+import android.util.Patterns;
 
 import com.android.internal.util.CollectionUtils;
 import com.android.server.compat.PlatformCompat;
@@ -29,9 +31,13 @@
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.util.Set;
+import java.util.regex.Matcher;
 
 public final class DomainVerificationUtils {
 
+    private static final ThreadLocal<Matcher> sCachedMatcher = ThreadLocal.withInitial(
+            () -> Patterns.DOMAIN_NAME.matcher(""));
+
     /**
      * Consolidates package exception messages. A generic unavailable message is included since the
      * caller doesn't bother to check why the package isn't available.
@@ -48,6 +54,15 @@
             return false;
         }
 
+        String host = intent.getData().getHost();
+        if (TextUtils.isEmpty(host)) {
+            return false;
+        }
+
+        if (!sCachedMatcher.get().reset(host).matches()) {
+            return false;
+        }
+
         Set<String> categories = intent.getCategories();
         int categoriesSize = CollectionUtils.size(categories);
         if (categoriesSize > 2) {
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 4adcfb6..9e19f57 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -613,9 +613,11 @@
                 PackageInstaller.SessionInfo session = mContext.getPackageManager()
                         .getPackageInstaller().getSessionInfo(rollback.getStagedSessionId());
                 if (session == null || session.isStagedSessionFailed()) {
-                    iter.remove();
-                    deleteRollback(rollback,
-                            "Session " + rollback.getStagedSessionId() + " not existed or failed");
+                    if (rollback.isEnabling()) {
+                        iter.remove();
+                        deleteRollback(rollback, "Session " + rollback.getStagedSessionId()
+                                + " not existed or failed");
+                    }
                     continue;
                 }
 
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 20c1c3c..14cab38 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -120,6 +120,7 @@
 
     @Override
     public boolean updateConfiguration(TimeConfiguration timeConfiguration) {
+        enforceManageTimeDetectorPermission();
         // TODO(b/172891783) Add actual logic
         return false;
     }
diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
index a59b368..48ccad33 100644
--- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
+++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
@@ -119,18 +119,18 @@
         if (!mIsQuitting) {
             mRouteSelectionCallback = new RouteSelectionCallback();
             mConnectivityManager.requestBackgroundNetwork(
-                    getRouteSelectionRequest(), mHandler, mRouteSelectionCallback);
+                    getRouteSelectionRequest(), mRouteSelectionCallback, mHandler);
 
             mWifiBringupCallback = new NetworkBringupCallback();
             mConnectivityManager.requestBackgroundNetwork(
-                    getWifiNetworkRequest(), mHandler, mWifiBringupCallback);
+                    getWifiNetworkRequest(), mWifiBringupCallback, mHandler);
 
             for (final int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) {
                 final NetworkBringupCallback cb = new NetworkBringupCallback();
                 mCellBringupCallbacks.add(cb);
 
                 mConnectivityManager.requestBackgroundNetwork(
-                        getCellNetworkRequestForSubId(subId), mHandler, cb);
+                        getCellNetworkRequestForSubId(subId), cb, mHandler);
             }
         } else {
             mRouteSelectionCallback = null;
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index e4dc8c2..f021072 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -96,6 +96,8 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
+import com.android.server.apphibernation.AppHibernationManagerInternal;
+import com.android.server.apphibernation.AppHibernationService;
 
 import java.util.ArrayList;
 import java.util.LinkedList;
@@ -163,6 +165,9 @@
      */
     private final LaunchObserverRegistryImpl mLaunchObserver;
     @VisibleForTesting static final int LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE = 512;
+    private final ArrayMap<String, Boolean> mLastHibernationStates = new ArrayMap<>();
+    private AppHibernationManagerInternal mAppHibernationManagerInternal;
+    private boolean mIsAppHibernationEnabled;
 
     /**
      * The information created when an intent is incoming but we do not yet know whether it will be
@@ -789,6 +794,27 @@
         }
     }
 
+    @Nullable
+    private AppHibernationManagerInternal getAppHibernationManagerInternal() {
+        if (mAppHibernationManagerInternal == null) {
+            mIsAppHibernationEnabled = AppHibernationService.isAppHibernationEnabled();
+            mAppHibernationManagerInternal =
+                    LocalServices.getService(AppHibernationManagerInternal.class);
+        }
+        return mAppHibernationManagerInternal;
+    }
+
+    /**
+     * Notifies the tracker before the package is unstopped because of launching activity.
+     * @param packageName The package to be unstopped.
+     */
+    void notifyBeforePackageUnstopped(@NonNull String packageName) {
+        final AppHibernationManagerInternal ahmInternal = getAppHibernationManagerInternal();
+        if (ahmInternal != null && mIsAppHibernationEnabled) {
+            mLastHibernationStates.put(packageName, ahmInternal.isHibernatingGlobally(packageName));
+        }
+    }
+
     /**
      * Notifies the tracker that we called immediately before we call bindApplication on the client.
      *
@@ -823,6 +849,8 @@
         }
 
         stopLaunchTrace(info);
+        final Boolean isHibernating =
+                mLastHibernationStates.remove(info.mLastLaunchedActivity.packageName);
         if (abort) {
             mSupervisor.stopWaitingForActivityVisible(info.mLastLaunchedActivity);
             launchObserverNotifyActivityLaunchCancelled(info);
@@ -830,7 +858,7 @@
             if (info.isInterestingToLoggerAndObserver()) {
                 launchObserverNotifyActivityLaunchFinished(info, timestampNs);
             }
-            logAppTransitionFinished(info);
+            logAppTransitionFinished(info, isHibernating != null ? isHibernating : false);
         }
         info.mPendingDrawActivities.clear();
         mTransitionInfoList.remove(info);
@@ -859,7 +887,7 @@
         }
     }
 
-    private void logAppTransitionFinished(@NonNull TransitionInfo info) {
+    private void logAppTransitionFinished(@NonNull TransitionInfo info, boolean isHibernating) {
         if (DEBUG_METRICS) Slog.i(TAG, "logging finished transition " + info);
 
         // Take a snapshot of the transition info before sending it to the handler for logging.
@@ -868,7 +896,7 @@
         if (info.isInterestingToLoggerAndObserver()) {
             BackgroundThread.getHandler().post(() -> logAppTransition(
                     info.mCurrentTransitionDeviceUptime, info.mCurrentTransitionDelayMs,
-                    infoSnapshot));
+                    infoSnapshot, isHibernating));
         }
         BackgroundThread.getHandler().post(() -> logAppDisplayed(infoSnapshot));
         if (info.mPendingFullyDrawn != null) {
@@ -880,7 +908,7 @@
 
     // This gets called on a background thread without holding the activity manager lock.
     private void logAppTransition(int currentTransitionDeviceUptime, int currentTransitionDelayMs,
-            TransitionInfoSnapshot info) {
+            TransitionInfoSnapshot info, boolean isHibernating) {
         final LogMaker builder = new LogMaker(APP_TRANSITION);
         builder.setPackageName(info.packageName);
         builder.setType(info.type);
@@ -933,7 +961,8 @@
                 packageOptimizationInfo.getCompilationReason(),
                 packageOptimizationInfo.getCompilationFilter(),
                 info.sourceType,
-                info.sourceEventDelayMs);
+                info.sourceEventDelayMs,
+                isHibernating);
 
         if (DEBUG_METRICS) {
             Slog.i(TAG, String.format("APP_START_OCCURRED(%s, %s, %s, %s, %s)",
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f5cfc9d..f3afff8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -587,7 +587,14 @@
 
     AnimatingActivityRegistry mAnimatingActivityRegistry;
 
-    private Task mLastParent;
+    // Set whenever the ActivityRecord gets reparented to a Task so we can know the last known
+    // parent was when the ActivityRecord is detached from the hierarchy
+    private Task mLastKnownParent;
+
+    // Set to the previous Task parent of the ActivityRecord when it is reparented to a new Task
+    // due to picture-in-picture. This gets cleared whenever this activity or the Task
+    // it references to gets removed. This should also be cleared when we move out of pip.
+    private Task mLastParentBeforePip;
 
     boolean firstWindowDrawn;
     /** Whether the visible window(s) of this activity is drawn. */
@@ -1096,6 +1103,9 @@
                 pw.println(prefix + "configChanges=0x" + Integer.toHexString(info.configChanges));
             }
         }
+        if (mLastParentBeforePip != null) {
+            pw.println(prefix + "lastParentTaskIdBeforePip=" + mLastParentBeforePip.mTaskId);
+        }
 
         dumpLetterboxInfo(pw, prefix);
     }
@@ -1133,6 +1143,8 @@
             pw.println(prefix + "  letterboxBackgroundWallpaperBlurRadius="
                     + getLetterboxWallpaperBlurRadius());
         }
+        pw.println(prefix + "  letterboxHorizontalPositionMultiplier="
+                + mWmService.getLetterboxHorizontalPositionMultiplier());
     }
 
     /**
@@ -1349,7 +1361,7 @@
             if (getDisplayContent() != null) {
                 getDisplayContent().mClosingApps.remove(this);
             }
-        } else if (mLastParent != null && mLastParent.getRootTask() != null) {
+        } else if (mLastKnownParent != null && mLastKnownParent.getRootTask() != null) {
             task.getRootTask().mExitingActivities.remove(this);
         }
         final Task rootTask = getRootTask();
@@ -1362,7 +1374,11 @@
                 ? rootTask.getAnimatingActivityRegistry()
                 : null;
 
-        mLastParent = task;
+        mLastKnownParent = task;
+        if (mLastKnownParent == mLastParentBeforePip) {
+            // Activity's reparented back from pip, clear the links once established
+            clearLastParentBeforePip();
+        }
 
         updateColorTransform();
 
@@ -1381,6 +1397,26 @@
         }
     }
 
+    /**
+     * Sets {@link #mLastParentBeforePip} to the current parent Task, it's caller's job to ensure
+     * {@link #getTask()} is set before this is called.
+     */
+    void setLastParentBeforePip() {
+        mLastParentBeforePip = getTask();
+        mLastParentBeforePip.mChildPipActivity = this;
+    }
+
+    private void clearLastParentBeforePip() {
+        if (mLastParentBeforePip != null) {
+            mLastParentBeforePip.mChildPipActivity = null;
+            mLastParentBeforePip = null;
+        }
+    }
+
+    @Nullable Task getLastParentBeforePip() {
+        return mLastParentBeforePip;
+    }
+
     private void updateColorTransform() {
         if (mSurfaceControl != null && mLastAppSaturationInfo != null) {
             getPendingTransaction().setColorTransform(mSurfaceControl,
@@ -2140,7 +2176,6 @@
             ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Add starting %s: startingData=%s",
                     this, startingData);
 
-
             WindowManagerPolicy.StartingSurface surface = null;
             try {
                 surface = startingData.createStartingSurface(ActivityRecord.this);
@@ -3434,6 +3469,7 @@
      */
     void cleanUp(boolean cleanServices, boolean setState) {
         task.cleanUpActivityReferences(this);
+        clearLastParentBeforePip();
 
         deferRelaunchUntilPaused = false;
         frozenBeforeDestroy = false;
@@ -7028,11 +7064,13 @@
         // and back which can cause visible issues (see b/184078928).
         final int parentWindowingMode =
                 newParentConfiguration.windowConfiguration.getWindowingMode();
+        final boolean isFixedOrientationLetterboxAllowed =
+                isSplitScreenWindowingMode(parentWindowingMode)
+                        || parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
+                        || parentWindowingMode == WINDOWING_MODE_FULLSCREEN;
         // TODO(b/181207944): Consider removing the if condition and always run
         // resolveFixedOrientationConfiguration() since this should be applied for all cases.
-        if (isSplitScreenWindowingMode(parentWindowingMode)
-                || parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
-                || parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+        if (isFixedOrientationLetterboxAllowed) {
             resolveFixedOrientationConfiguration(newParentConfiguration);
         }
 
@@ -7053,12 +7091,16 @@
             resolveFullscreenConfiguration(newParentConfiguration);
         }
 
+        if (isFixedOrientationLetterboxAllowed || mCompatDisplayInsets != null
+                // In fullscreen, can be letterboxed for aspect ratio.
+                || !inMultiWindowMode()) {
+            updateResolvedBoundsHorizontalPosition(newParentConfiguration);
+        }
+
         if (mVisibleRequested) {
             updateCompatDisplayInsets();
         }
 
-        // TODO(b/175212232): Consolidate position logic from each "resolve" method above here.
-
         // Assign configuration sequence number into hierarchy because there is a different way than
         // ensureActivityConfiguration() in this class that uses configuration in WindowState during
         // layout traversals.
@@ -7089,6 +7131,47 @@
         }
     }
 
+
+    /**
+     * Adjusts horizontal position of resolved bounds if they doesn't fill the parent using gravity
+     * requested in the config or via an ADB command. For more context see {@link
+     * WindowManagerService#getLetterboxHorizontalPositionMultiplier}.
+     */
+    private void updateResolvedBoundsHorizontalPosition(Configuration newParentConfiguration) {
+        final Configuration resolvedConfig = getResolvedOverrideConfiguration();
+        final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
+        final Rect screenResolvedBounds =
+                mSizeCompatBounds != null ? mSizeCompatBounds : resolvedBounds;
+        final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
+        final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
+        if (resolvedBounds.isEmpty() || parentBounds.width() == screenResolvedBounds.width()) {
+            return;
+        }
+
+        int offsetX = 0;
+        if (screenResolvedBounds.width() >= parentAppBounds.width()) {
+            // If resolved bounds overlap with insets, center within app bounds.
+            offsetX = getHorizontalCenterOffset(
+                    parentAppBounds.width(), screenResolvedBounds.width());
+        } else {
+            float positionMultiplier = mWmService.getLetterboxHorizontalPositionMultiplier();
+            positionMultiplier =
+                    (positionMultiplier < 0.0f || positionMultiplier > 1.0f)
+                            // Default to central position if invalid value is provided.
+                            ? 0.5f : positionMultiplier;
+            offsetX = (int) Math.ceil((parentAppBounds.width() - screenResolvedBounds.width())
+                    * positionMultiplier);
+        }
+
+        if (mSizeCompatBounds != null) {
+            mSizeCompatBounds.offset(offsetX, 0 /* offsetY */);
+            final int dx = mSizeCompatBounds.left - resolvedBounds.left;
+            offsetBounds(resolvedConfig, dx,  0 /* offsetY */);
+        } else {
+            offsetBounds(resolvedConfig, offsetX, 0 /* offsetY */);
+        }
+    }
+
     /**
      * Whether this activity is letterboxed for fixed orientation. If letterboxed due to fixed
      * orientation then aspect ratio restrictions are also already respected.
@@ -7166,7 +7249,10 @@
             resolvedBounds.set(parentBounds.left, top, parentBounds.right, top + height);
         } else {
             final int width = (int) Math.rint(parentHeight / aspect);
-            final int left = parentBounds.centerX() - width / 2;
+            final Rect parentAppBounds = newParentConfig.windowConfiguration.getAppBounds();
+            final int left = width <= parentAppBounds.width()
+                    // Avoid overlapping with the horizontal decor area when possible.
+                    ? parentAppBounds.left : parentBounds.centerX() - width / 2;
             resolvedBounds.set(left, parentBounds.top, left + width, parentBounds.bottom);
         }
 
@@ -7218,12 +7304,6 @@
             task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
                     getFixedRotationTransformDisplayInfo());
         }
-        if (needToBeCentered) {
-            // Offset to center relative to parent's app bounds.
-            final int offsetX = getHorizontalCenterOffset(
-                    parentAppBounds.width(), resolvedBounds.width());
-            offsetBounds(resolvedConfig, offsetX, 0 /* offsetY */);
-        }
     }
 
     /**
@@ -7321,8 +7401,8 @@
 
         final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds();
 
-        // Calculates the scale and offset to horizontal center the size compatibility bounds into
-        // the region which is available to application.
+        // Calculates the scale the size compatibility bounds into the region which is available
+        // to application.
         final int contentW = resolvedAppBounds.width();
         final int contentH = resolvedAppBounds.height();
         final int viewportW = containerAppBounds.width();
@@ -7330,8 +7410,9 @@
         // Only allow to scale down.
         mSizeCompatScale = (contentW <= viewportW && contentH <= viewportH)
                 ? 1f : Math.min((float) viewportW / contentW, (float) viewportH / contentH);
-        final int screenTopInset = containerAppBounds.top - containerBounds.top;
-        final boolean topNotAligned = screenTopInset != resolvedAppBounds.top - resolvedBounds.top;
+        final int containerTopInset = containerAppBounds.top - containerBounds.top;
+        final boolean topNotAligned =
+                containerTopInset != resolvedAppBounds.top - resolvedBounds.top;
         if (mSizeCompatScale != 1f || topNotAligned) {
             if (mSizeCompatBounds == null) {
                 mSizeCompatBounds = new Rect();
@@ -7340,18 +7421,15 @@
             mSizeCompatBounds.offsetTo(0, 0);
             mSizeCompatBounds.scale(mSizeCompatScale);
             // The insets are included in height, e.g. the area of real cutout shouldn't be scaled.
-            mSizeCompatBounds.bottom += screenTopInset;
+            mSizeCompatBounds.bottom += containerTopInset;
         } else {
             mSizeCompatBounds = null;
         }
 
-        // Center horizontally in parent (app bounds) and align to top of parent (bounds)
-        // - this is a UX choice.
-        final int offsetX = getHorizontalCenterOffset(
-                (int) viewportW, (int) (contentW * mSizeCompatScale));
+        // Align to top of parent (bounds) - this is a UX choice and exclude the horizontal decor
+        // if needed. Horizontal position is adjusted in updateResolvedBoundsHorizontalPosition.
         // Above coordinates are in "@" space, now place "*" and "#" to screen space.
-        final int screenPosX = (fillContainer
-                ? containerBounds.left : containerAppBounds.left) + offsetX;
+        final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left;
         final int screenPosY = containerBounds.top;
         if (screenPosX != 0 || screenPosY != 0) {
             if (mSizeCompatBounds != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 75a188e..1158a9c 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2721,8 +2721,22 @@
                     launchFlags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
                     break;
                 case ActivityInfo.DOCUMENT_LAUNCH_NEVER:
-                    launchFlags &=
-                            ~(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK);
+                    if (mLaunchMode == LAUNCH_SINGLE_INSTANCE_PER_TASK) {
+                        // Remove MULTIPLE_TASK flag along with NEW_DOCUMENT only if NEW_DOCUMENT
+                        // is set, otherwise we still want to keep the MULTIPLE_TASK flag (if
+                        // any) for singleInstancePerTask that the multiple tasks can be created,
+                        // or a singleInstancePerTask activity is basically the same as a
+                        // singleTask activity when documentLaunchMode set to never.
+                        if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0) {
+                            launchFlags &= ~(Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+                                    | FLAG_ACTIVITY_MULTIPLE_TASK);
+                        }
+                    } else {
+                        // TODO(b/184903976): Should FLAG_ACTIVITY_MULTIPLE_TASK always be
+                        // removed for document-never activity?
+                        launchFlags &=
+                                ~(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK);
+                    }
                     break;
             }
         }
diff --git a/services/core/java/com/android/server/wm/BlurController.java b/services/core/java/com/android/server/wm/BlurController.java
index 128d452..d920267 100644
--- a/services/core/java/com/android/server/wm/BlurController.java
+++ b/services/core/java/com/android/server/wm/BlurController.java
@@ -18,24 +18,52 @@
 
 import static android.view.CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED;
 
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.os.PowerManager;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.view.ICrossWindowBlurEnabledListener;
 
 import com.android.internal.annotations.GuardedBy;
 
+/**
+ * Keeps track of the different factors that determine whether cross-window blur is enabled
+ * or disabled. Also keeps a list of all interested listeners and notifies them when the
+ * blur enabled state changes.
+ */
 final class BlurController {
-
+    private final PowerManager mPowerManager;
     private final RemoteCallbackList<ICrossWindowBlurEnabledListener>
             mBlurEnabledListeners = new RemoteCallbackList<>();
+    // We don't use the WM global lock, because the BlurController is not involved in window
+    // drawing and only receives binder calls that don't need synchronization with the rest of WM
     private final Object mLock = new Object();
     @GuardedBy("mLock")
     boolean mBlurEnabled;
     @GuardedBy("mLock")
     boolean mBlurForceDisabled;
+    @GuardedBy("mLock")
+    boolean mInBatterySaverMode;
 
-    BlurController() {
-        mBlurEnabled = CROSS_WINDOW_BLUR_SUPPORTED;
+    BlurController(Context context, PowerManager powerManager) {
+        mPowerManager = powerManager;
+        mInBatterySaverMode = mPowerManager.isPowerSaveMode();
+        updateBlurEnabledLocked();
+
+        IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
+        filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+        context.registerReceiverForAllUsers(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
+                    setBatterySaverEnabled(mPowerManager.isPowerSaveMode());
+                }
+            }
+        }, filter, null, null);
     }
 
     boolean registerCrossWindowBlurEnabledListener(ICrossWindowBlurEnabledListener listener) {
@@ -59,8 +87,16 @@
 
     }
 
+    void setBatterySaverEnabled(boolean enabled) {
+        synchronized (mLock) {
+            mInBatterySaverMode = enabled;
+            updateBlurEnabledLocked();
+        }
+    }
+
     private void updateBlurEnabledLocked() {
-        final boolean newEnabled = CROSS_WINDOW_BLUR_SUPPORTED && !mBlurForceDisabled;
+        final boolean newEnabled = CROSS_WINDOW_BLUR_SUPPORTED && !mBlurForceDisabled
+                && !mInBatterySaverMode;
         if (mBlurEnabled == newEnabled) {
             return;
         }
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 6631a3e..4bb48b0b 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1415,6 +1415,9 @@
             return true;
         }
 
+        // The given task if always treated as in visible range if it is the origin of pinned task.
+        if (task.mChildPipActivity != null) return true;
+
         if (mMaxNumVisibleTasks >= 0) {
             // Always keep up to the max number of recent tasks, but return false afterwards
             return numVisibleTasks <= mMaxNumVisibleTasks;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 52551ec..b9fc8b1 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2124,6 +2124,8 @@
                         .setDeferTaskAppear(true)
                         .setHasBeenVisible(true)
                         .build();
+                // Establish bi-directional link between the original and pinned task.
+                r.setLastParentBeforePip();
                 // It's possible the task entering PIP is in freeform, so save the last
                 // non-fullscreen bounds. Then when this new PIP task exits PIP, it can restore
                 // to its previous freeform bounds.
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 95a4f69e..fb66c04 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -36,9 +36,11 @@
 import android.animation.ArgbEvaluator;
 import android.content.Context;
 import android.graphics.Color;
+import android.graphics.GraphicBuffer;
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
 import android.os.Trace;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
@@ -210,9 +212,9 @@
             String name = "RotationLayer";
             mScreenshotLayer = displayContent.makeOverlay()
                     .setName(name)
-                    .setBufferSize(mWidth, mHeight)
                     .setSecure(isSecure)
                     .setCallsite("ScreenRotationAnimation")
+                    .setBLASTLayer()
                     .build();
             // This is the way to tell the input system to exclude this surface from occlusion
             // detection since we don't have a window for it. We do this because this window is
@@ -225,32 +227,29 @@
                     .setCallsite("ScreenRotationAnimation")
                     .build();
 
-            final Surface surface = mService.mSurfaceFactory.get();
-            // In case display bounds change, screenshot buffer and surface may mismatch so
-            // set a scaling mode.
-            surface.copyFrom(mScreenshotLayer);
-            surface.setScalingMode(Surface.SCALING_MODE_SCALE_TO_WINDOW);
-
+            HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
                     "ScreenRotationAnimation#getMedianBorderLuma");
-            mStartLuma = RotationAnimationUtils.getMedianBorderLuma(
-                    screenshotBuffer.getHardwareBuffer(), screenshotBuffer.getColorSpace());
+            mStartLuma = RotationAnimationUtils.getMedianBorderLuma(hardwareBuffer,
+                    screenshotBuffer.getColorSpace());
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-            try {
-                surface.attachAndQueueBufferWithColorSpace(screenshotBuffer.getHardwareBuffer(),
-                        screenshotBuffer.getColorSpace());
-            } catch (RuntimeException e) {
-                Slog.w(TAG, "Failed to attach screenshot - " + e.getMessage());
-            }
+
+            GraphicBuffer buffer = GraphicBuffer.createFromHardwareBuffer(
+                    screenshotBuffer.getHardwareBuffer());
+            // Scale the layer to the display size.
+            float dsdx = (float) mWidth / hardwareBuffer.getWidth();
+            float dsdy = (float) mHeight / hardwareBuffer.getHeight();
 
             t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE);
             t.reparent(mBackColorSurface, displayContent.getSurfaceControl());
             t.setLayer(mBackColorSurface, -1);
             t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
             t.setAlpha(mBackColorSurface, 1);
+            t.setBuffer(mScreenshotLayer, buffer);
+            t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());
+            t.setMatrix(mScreenshotLayer, dsdx, 0, 0, dsdy);
             t.show(mScreenshotLayer);
             t.show(mBackColorSurface);
-            surface.destroy();
 
         } catch (OutOfResourcesException e) {
             Slog.w(TAG, "Unable to allocate freeze surface", e);
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index 140ae3e..603bfd1 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -58,10 +58,12 @@
                     overrideConfig, displayId);
         }
 
-        final Task task = activity.getTask();
-        if (task != null && mService.mAtmService.mTaskOrganizerController.addStartingWindow(task,
-                activity.token, theme)) {
-            return new ShellStartingSurface(task);
+        synchronized (mService.mGlobalLock) {
+            final Task task = activity.getTask();
+            if (task != null && mService.mAtmService.mTaskOrganizerController.addStartingWindow(
+                    task, activity.token, theme)) {
+                return new ShellStartingSurface(task);
+            }
         }
         return null;
     }
@@ -124,14 +126,13 @@
                 activity.mDisplayContent.handleTopActivityLaunchingInDifferentOrientation(
                         topFullscreenActivity, false /* checkOpening */);
             }
+            if (DEBUG_ENABLE_SHELL_DRAWER) {
+                mService.mAtmService.mTaskOrganizerController.addStartingWindow(task,
+                        activity.token, 0 /* launchTheme */);
+                return new ShellStartingSurface(task);
+            }
         }
-        if (!DEBUG_ENABLE_SHELL_DRAWER) {
-            return mService.mTaskSnapshotController
-                    .createStartingSurface(activity, taskSnapshot);
-        }
-        mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, activity.token,
-                0 /* launchTheme */);
-        return new ShellStartingSurface(task);
+        return mService.mTaskSnapshotController.createStartingSurface(activity, taskSnapshot);
     }
 
 
@@ -144,8 +145,9 @@
 
         @Override
         public void remove(boolean animate) {
-            mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask,
-                    animate);
+            synchronized (mService.mGlobalLock) {
+                mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask, animate);
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java
index 6f434e0..e0a791e 100644
--- a/services/core/java/com/android/server/wm/SurfaceFreezer.java
+++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java
@@ -22,6 +22,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.GraphicBuffer;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
@@ -153,29 +154,24 @@
          */
         Snapshot(Supplier<Surface> surfaceFactory, SurfaceControl.Transaction t,
                 SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer, SurfaceControl parent) {
-            Surface drawSurface = surfaceFactory.get();
             // We can't use a delegating constructor since we need to
             // reference this::onAnimationFinished
-            HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
-            final int width = hardwareBuffer.getWidth();
-            final int height = hardwareBuffer.getHeight();
+            GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(
+                    screenshotBuffer.getHardwareBuffer());
 
             mSurfaceControl = mAnimatable.makeAnimationLeash()
                     .setName("snapshot anim: " + mAnimatable.toString())
-                    .setBufferSize(width, height)
                     .setFormat(PixelFormat.TRANSLUCENT)
                     .setParent(parent)
                     .setSecure(screenshotBuffer.containsSecureLayers())
                     .setCallsite("SurfaceFreezer.Snapshot")
+                    .setBLASTLayer()
                     .build();
 
             ProtoLog.i(WM_SHOW_TRANSACTIONS, "  THUMBNAIL %s: CREATE", mSurfaceControl);
 
-            // Transfer the thumbnail to the surface
-            drawSurface.copyFrom(mSurfaceControl);
-            drawSurface.attachAndQueueBufferWithColorSpace(hardwareBuffer,
-                    screenshotBuffer.getColorSpace());
-            drawSurface.release();
+            t.setBuffer(mSurfaceControl, graphicBuffer);
+            t.setColorSpace(mSurfaceControl, screenshotBuffer.getColorSpace());
             t.show(mSurfaceControl);
 
             // We parent the thumbnail to the container, and just place it on top of anything else
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8690499..858d9f3 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -131,6 +131,7 @@
 import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER;
 import static com.android.server.wm.TaskProto.DISPLAY_ID;
 import static com.android.server.wm.TaskProto.FILLS_PARENT;
+import static com.android.server.wm.TaskProto.HAS_CHILD_PIP_ACTIVITY;
 import static com.android.server.wm.TaskProto.LAST_NON_FULLSCREEN_BOUNDS;
 import static com.android.server.wm.TaskProto.MIN_HEIGHT;
 import static com.android.server.wm.TaskProto.MIN_WIDTH;
@@ -836,6 +837,14 @@
     // The task will be removed when TaskOrganizer, which is managing the task, is destroyed.
     boolean mRemoveWithTaskOrganizer;
 
+    /**
+     * Reference to the pinned activity that is logically parented to this task, ie.
+     * the previous top activity within this task is put into pinned mode.
+     * This always gets cleared in pair with the ActivityRecord-to-Task link as seen in
+     * {@link ActivityRecord#clearLastParentBeforePip()}.
+     */
+    ActivityRecord mChildPipActivity;
+
     private Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent,
             Intent _affinityIntent, String _affinity, String _rootAffinity,
             ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
@@ -1841,6 +1850,10 @@
 
     /** Completely remove all activities associated with an existing task. */
     void performClearTask(String reason) {
+        // The original task is to be removed, try remove also the pinned task.
+        if (mChildPipActivity != null && mChildPipActivity.getTask() != null) {
+            mTaskSupervisor.removeRootTask(mChildPipActivity.getTask());
+        }
         // Broken down into to cases to avoid object create due to capturing mStack.
         if (getRootTask() == null) {
             forAllActivities((r) -> {
@@ -4449,6 +4462,7 @@
         }
         pw.print(prefix); pw.print("taskId=" + mTaskId);
         pw.println(" rootTaskId=" + getRootTaskId());
+        pw.print(prefix); pw.println("hasChildPipActivity=" + (mChildPipActivity != null));
         pw.print(prefix); pw.print("mHasBeenVisible="); pw.println(getHasBeenVisible());
         pw.print(prefix); pw.print("mResizeMode=");
         pw.print(ActivityInfo.resizeModeToString(mResizeMode));
@@ -5328,7 +5342,6 @@
             return;
         }
         final int currentMode = getWindowingMode();
-        final int currentOverrideMode = getRequestedOverrideWindowingMode();
         final Task topTask = getTopMostTask();
         int windowingMode = preferredWindowingMode;
 
@@ -5397,9 +5410,26 @@
                 mTaskSupervisor.mNoAnimActivities.add(topActivity);
             }
             super.setWindowingMode(windowingMode);
-            // setWindowingMode triggers an onConfigurationChanged cascade which can result in a
-            // different resolved windowing mode (usually when preferredWindowingMode is UNDEFINED).
-            windowingMode = getWindowingMode();
+
+            // Try reparent pinned activity back to its original task after onConfigurationChanged
+            // cascade finishes. This is done on Task level instead of
+            // {@link ActivityRecord#onConfigurationChanged(Configuration)} since when we exit PiP,
+            // we set final windowing mode on the ActivityRecord first and then on its Task when
+            // the exit PiP transition finishes. Meanwhile, the exit transition is always
+            // performed on its original task, reparent immediately in ActivityRecord breaks it.
+            if (currentMode == WINDOWING_MODE_PINNED) {
+                if (topActivity != null && topActivity.getLastParentBeforePip() != null) {
+                    // Do not reparent if the pinned task is in removal, indicated by the
+                    // force hidden flag.
+                    if (!isForceHidden()) {
+                        final Task lastParentBeforePip = topActivity.getLastParentBeforePip();
+                        topActivity.reparent(lastParentBeforePip,
+                                lastParentBeforePip.getChildCount() /* top */,
+                                "movePinnedActivityToOriginalTask");
+                        lastParentBeforePip.moveToFront("movePinnedActivityToOriginalTask");
+                    }
+                }
+            }
 
             if (creating) {
                 // Nothing else to do if we don't have a window container yet. E.g. call from ctor.
@@ -6279,6 +6309,8 @@
         // Launching this app's activity, make sure the app is no longer
         // considered stopped.
         try {
+            mTaskSupervisor.getActivityMetricsLogger()
+                    .notifyBeforePackageUnstopped(next.packageName);
             mAtmService.getPackageManager().setPackageStoppedState(
                     next.packageName, false, next.mUserId); /* TODO: Verify if correct userid */
         } catch (RemoteException e1) {
@@ -7530,7 +7562,11 @@
             final Task task = getBottomMostTask();
             setWindowingMode(WINDOWING_MODE_UNDEFINED);
 
-            getDisplayArea().positionChildAt(POSITION_TOP, this, false /* includingParents */);
+            // Task could have been removed from the hierarchy due to windowing mode change
+            // where its only child is reparented back to their original parent task.
+            if (isAttached()) {
+                getDisplayArea().positionChildAt(POSITION_TOP, this, false /* includingParents */);
+            }
 
             mTaskSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(task, this);
         });
@@ -7831,6 +7867,7 @@
 
         proto.write(CREATED_BY_ORGANIZER, mCreatedByOrganizer);
         proto.write(AFFINITY, affinity);
+        proto.write(HAS_CHILD_PIP_ACTIVITY, mChildPipActivity != null);
 
         proto.end(token);
     }
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 5ffab48..a467d82 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -646,8 +646,12 @@
         if (shouldDisableSnapshots()) {
             return;
         }
+        final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId);
+        if (displayContent == null) {
+            return;
+        }
         mTmpTasks.clear();
-        mService.mRoot.getDisplayContent(displayId).forAllTasks(task -> {
+        displayContent.forAllTasks(task -> {
             // Since RecentsAnimation will handle task snapshot while switching apps with the best
             // capture timing (e.g. IME window capture), No need additional task capture while task
             // is controlled by RecentsAnimation.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7650fa1..0c9473a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -731,7 +731,7 @@
 
     final TaskSnapshotController mTaskSnapshotController;
 
-    final BlurController mBlurController = new BlurController();
+    final BlurController mBlurController;
 
     boolean mIsTouchDevice;
     boolean mIsFakeTouchDevice;
@@ -1024,6 +1024,10 @@
     // Values < 0 or >= 1 are ignored and 0.0 (transparent) is used instead.
     private float mLetterboxBackgroundWallpaperDarkScrimAlpha;
 
+    // horizontal position of a center of the letterboxed app window. 0 corresponds to the left
+    // side of the screen and 1.0 to the right side.
+    private float mLetterboxHorizontalPositionMultiplier;
+
     final InputManagerService mInputManager;
     final DisplayManagerInternal mDisplayManagerInternal;
     final DisplayManager mDisplayManager;
@@ -1265,6 +1269,8 @@
                 com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius);
         mLetterboxBackgroundWallpaperDarkScrimAlpha = context.getResources().getFloat(
                 com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha);
+        mLetterboxHorizontalPositionMultiplier = context.getResources().getFloat(
+                com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier);
 
         mInputManager = inputManager; // Must be before createDisplayContentLocked.
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
@@ -1418,6 +1424,8 @@
         setGlobalShadowSettings();
         mAnrController = new AnrController(this);
         mStartingSurfaceController = new StartingSurfaceController(this);
+
+        mBlurController = new BlurController(mContext, mPowerManager);
     }
 
     DisplayAreaPolicy.Provider getDisplayAreaPolicyProvider() {
@@ -1740,8 +1748,11 @@
             }
 
             // Switch to listen to the {@link WindowToken token}'s configuration changes when
-            // adding a window to the window context.
-            if (mWindowContextListenerController.hasListener(windowContextToken)) {
+            // adding a window to the window context. Filter sub window type here because the sub
+            // window must be attached to the parent window, which is attached to the window context
+            // created window token.
+            if (!win.isChildWindow()
+                    && mWindowContextListenerController.hasListener(windowContextToken)) {
                 final int windowContextType = mWindowContextListenerController
                         .getWindowType(windowContextToken);
                 if (type != windowContextType) {
@@ -4053,6 +4064,38 @@
         return mLetterboxBackgroundWallpaperBlurRadius;
     }
 
+    /*
+     * Gets horizontal position of a center of the letterboxed app window specified
+     * in {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}
+     * or via an ADB command. 0 corresponds to the left side of the screen and 1 to the
+     * right side.
+     *
+     * <p>This value can be outside of [0, 1] range so clients need to check and default to the
+     * central position (0.5).
+     */
+    float getLetterboxHorizontalPositionMultiplier() {
+        return mLetterboxHorizontalPositionMultiplier;
+    }
+
+    /**
+     * Overrides horizontal position of a center of the letterboxed app window. If given value < 0
+     * or > 1, then it and a value of {@link
+     * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier} are ignored and
+     * central position (0.5) is used.
+     */
+    void setLetterboxHorizontalPositionMultiplier(float multiplier) {
+        mLetterboxHorizontalPositionMultiplier = multiplier;
+    }
+
+    /**
+     * Resets horizontal position of a center of the letterboxed app window to {@link
+     * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}.
+     */
+    void resetLetterboxHorizontalPositionMultiplier() {
+        mLetterboxHorizontalPositionMultiplier = mContext.getResources().getFloat(
+                com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier);
+    }
+
     @Override
     public void setIgnoreOrientationRequest(int displayId, boolean ignoreOrientationRequest) {
         if (!checkCallingPermission(
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 5942f34..68257d4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -143,6 +143,10 @@
                     return runSetLetterboxBackgroundWallpaperDarkScrimAlpha(pw);
                 case "get-letterbox-background-wallpaper-dark-scrim-alpha":
                     return runGetLetterboxBackgroundWallpaperDarkScrimAlpha(pw);
+                case "set-letterbox-horizontal-position-multiplier":
+                    return runSeLetterboxHorizontalPositionMultiplier(pw);
+                case "get-letterbox-horizontal-position-multiplier":
+                    return runGetLetterboxHorizontalPositionMultiplier(pw);
                 case "set-sandbox-display-apis":
                     return runSandboxDisplayApis(pw);
                 case "reset":
@@ -846,6 +850,43 @@
         return 0;
     }
 
+    private int runSeLetterboxHorizontalPositionMultiplier(PrintWriter pw) throws RemoteException {
+        final float multiplier;
+        try {
+            String arg = getNextArgRequired();
+            if ("reset".equals(arg)) {
+                synchronized (mInternal.mGlobalLock) {
+                    mInternal.resetLetterboxHorizontalPositionMultiplier();
+                }
+                return 0;
+            }
+            multiplier = Float.parseFloat(arg);
+        } catch (NumberFormatException  e) {
+            getErrPrintWriter().println("Error: bad multiplier format " + e);
+            return -1;
+        } catch (IllegalArgumentException  e) {
+            getErrPrintWriter().println(
+                    "Error: 'reset' or multiplier should be provided as an argument " + e);
+            return -1;
+        }
+        synchronized (mInternal.mGlobalLock) {
+            mInternal.setLetterboxHorizontalPositionMultiplier(multiplier);
+        }
+        return 0;
+    }
+
+    private int runGetLetterboxHorizontalPositionMultiplier(PrintWriter pw) throws RemoteException {
+        synchronized (mInternal.mGlobalLock) {
+            final float multiplier = mInternal.getLetterboxHorizontalPositionMultiplier();
+            if (multiplier < 0) {
+                pw.println("Letterbox horizontal position multiplier is not set");
+            } else {
+                pw.println("Letterbox horizontal position multiplier is " + multiplier);
+            }
+        }
+        return 0;
+    }
+
     private int runReset(PrintWriter pw) throws RemoteException {
         int displayId = getDisplayId(getNextArg());
 
@@ -888,6 +929,9 @@
         // set-letterbox-background-wallpaper-dark-scrim-alpha
         mInternal.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
 
+        // set-letterbox-horizontal-position-multiplier
+        mInternal.resetLetterboxHorizontalPositionMultiplier();
+
         // set-sandbox-display-apis
         mInternal.setSandboxDisplayApis(displayId, /* sandboxDisplayApis= */ true);
 
@@ -954,6 +998,11 @@
         pw.println("    letterbox background. If alpha < 0 or >= 1 both it and");
         pw.println("    R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha are ignored and ");
         pw.println("    0.0 (transparent) is used instead.");
+        pw.println("  set-letterbox-horizontal-position-multiplier [reset|multiplier]");
+        pw.println("  get-letterbox-horizontal-position-multiplier");
+        pw.println("    horizontal position of a center of a letterboxed app. If it < 0 or > 1");
+        pw.println("    then both it and R.dimen.config_letterboxHorizontalPositionMultiplier");
+        pw.println("    are ignored and central position (0.5) is used.");
         pw.println("  set-sandbox-display-apis [true|1|false|0]");
         pw.println("    Sets override of Display APIs getRealSize / getRealMetrics to reflect ");
         pw.println("    DisplayArea of the activity, or the window bounds if in letterbox or");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 2e37fee..9382b8e 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -36,6 +36,7 @@
 import android.app.WindowConfiguration;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
+import android.graphics.GraphicBuffer;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.os.Binder;
@@ -45,7 +46,6 @@
 import android.os.RemoteException;
 import android.util.ArraySet;
 import android.util.Slog;
-import android.view.Surface;
 import android.view.SurfaceControl;
 import android.window.IDisplayAreaOrganizerController;
 import android.window.ITaskOrganizerController;
@@ -766,18 +766,21 @@
             return false;
         }
 
+        GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(
+                buffer.getHardwareBuffer());
         SurfaceControl screenshot = mService.mWindowManager.mSurfaceControlFactory.apply(null)
                 .setName(wc.getName() + " - Organizer Screenshot")
-                .setBufferSize(bounds.width(), bounds.height())
                 .setFormat(PixelFormat.TRANSLUCENT)
                 .setParent(wc.getParentSurfaceControl())
+                .setSecure(buffer.containsSecureLayers())
                 .setCallsite("WindowOrganizerController.takeScreenshot")
+                .setBLASTLayer()
                 .build();
 
-        Surface surface = new Surface();
-        surface.copyFrom(screenshot);
-        surface.attachAndQueueBufferWithColorSpace(buffer.getHardwareBuffer(), null);
-        surface.release();
+        SurfaceControl.Transaction transaction = mService.mWindowManager.mTransactionFactory.get();
+        transaction.setBuffer(screenshot, graphicBuffer);
+        transaction.setColorSpace(screenshot, buffer.getColorSpace());
+        transaction.apply();
 
         outSurfaceControl.copyFrom(screenshot, "WindowOrganizerController.takeScreenshot");
         return true;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ddcb2bf..6283b4e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -96,6 +96,7 @@
 import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED;
 import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED;
 import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_STARTING_PROFILE_FAILED;
+import static android.app.admin.DevicePolicyManager.STATE_USER_UNMANAGED;
 import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
 import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
 import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
@@ -8517,20 +8518,16 @@
                     + " as profile owner for user " + userHandle);
             return false;
         }
-        if (who == null
-                || !isPackageInstalledForUser(who.getPackageName(), userHandle)) {
-            throw new IllegalArgumentException("Component " + who
-                    + " not installed for userId:" + userHandle);
-        }
+        Preconditions.checkArgument(who != null);
 
         final CallerIdentity caller = getCallerIdentity();
         synchronized (getLockObject()) {
             enforceCanSetProfileOwnerLocked(caller, who, userHandle);
-
+            Preconditions.checkArgument(isPackageInstalledForUser(who.getPackageName(), userHandle),
+                    "Component " + who + " not installed for userId:" + userHandle);
             final ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle);
-            if (admin == null || getUserData(userHandle).mRemovingAdmins.contains(who)) {
-                throw new IllegalArgumentException("Not active admin: " + who);
-            }
+            Preconditions.checkArgument(admin != null && !getUserData(
+                    userHandle).mRemovingAdmins.contains(who), "Not active admin: " + who);
 
             final int parentUserId = getProfileParentId(userHandle);
             // When trying to set a profile owner on a new user, it may be that this user is
@@ -8739,7 +8736,8 @@
 
         final CallerIdentity caller = getCallerIdentity();
         if (userHandle != mOwners.getDeviceOwnerUserId() && !mOwners.hasProfileOwner(userHandle)
-                && getManagedUserId(userHandle) == -1) {
+                && getManagedUserId(userHandle) == -1
+                && newState != STATE_USER_UNMANAGED) {
             // No managed device, user or profile, so setting provisioning state makes no sense.
             throw new IllegalStateException("Not allowed to change provisioning state unless a "
                       + "device or profile owner is set.");
@@ -8802,6 +8800,12 @@
             case DevicePolicyManager.STATE_USER_SETUP_FINALIZED:
                 // Cannot transition out of finalized.
                 break;
+            case DevicePolicyManager.STATE_USER_PROFILE_FINALIZED:
+                // Should only move to an unmanaged state after removing the work profile.
+                if (newState == DevicePolicyManager.STATE_USER_UNMANAGED) {
+                    return;
+                }
+                break;
         }
 
         // Didn't meet any of the accepted state transition checks above, throw appropriate error.
diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp
index b136d00..83677c2 100644
--- a/services/tests/PackageManagerServiceTests/host/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/Android.bp
@@ -36,6 +36,10 @@
     ],
     test_suites: ["general-tests"],
     java_resources: [
+        ":PackageManagerTestOverlayActor",
+        ":PackageManagerTestOverlay",
+        ":PackageManagerTestOverlayTarget",
+        ":PackageManagerTestOverlayTargetNoOverlayable",
         ":PackageManagerTestAppDeclaresStaticLibrary",
         ":PackageManagerTestAppStub",
         ":PackageManagerTestAppUsesStaticLibrary",
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayActorVisibilityTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayActorVisibilityTest.kt
new file mode 100644
index 0000000..558d01ed
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayActorVisibilityTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test
+
+import com.android.internal.util.test.SystemPreparer
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.After
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+import java.io.File
+
+@RunWith(DeviceJUnit4ClassRunner::class)
+class OverlayActorVisibilityTest : BaseHostJUnit4Test() {
+
+    companion object {
+        private const val ACTOR_PKG_NAME = "com.android.server.pm.test.overlay.actor"
+        private const val ACTOR_APK = "PackageManagerTestOverlayActor.apk"
+        private const val TARGET_APK = "PackageManagerTestOverlayTarget.apk"
+        private const val OVERLAY_APK = "PackageManagerTestOverlay.apk"
+        private const val TARGET_NO_OVERLAYABLE_APK =
+            "PackageManagerTestOverlayTargetNoOverlayable.apk"
+
+        @get:ClassRule
+        val deviceRebootRule = SystemPreparer.TestRuleDelegate(true)
+    }
+
+    @get:Rule
+    val tempFolder = TemporaryFolder()
+
+    private val preparer: SystemPreparer = SystemPreparer(
+        tempFolder,
+        SystemPreparer.RebootStrategy.FULL,
+        deviceRebootRule
+    ) { this.device }
+
+    private val namedActorFile = File(
+        "/system/etc/sysconfig/com.android.server.pm.test.OverlayActorVisibilityTest.xml"
+    )
+
+    @Before
+    @After
+    fun uninstallPackages() {
+        device.uninstallPackages(ACTOR_APK, TARGET_APK, OVERLAY_APK)
+    }
+
+    @Before
+    fun pushSysConfigFile() {
+        // In order for the test app to be the verification agent, it needs a permission file
+        // which can be pushed onto the system and removed afterwards.
+        // language=XML
+        val file = tempFolder.newFile().apply {
+            """
+            <config>
+                <named-actor
+                    namespace="androidTest"
+                    name="OverlayActorVisibilityTest"
+                    package="$ACTOR_PKG_NAME"
+                    />
+            </config>
+            """
+                .trimIndent()
+                .let { writeText(it) }
+        }
+
+        preparer.pushFile(file, namedActorFile.toString())
+            .reboot()
+    }
+
+    @After
+    fun deleteSysConfigFile() {
+        preparer.deleteFile(namedActorFile.toString())
+            .reboot()
+    }
+
+    @Test
+    fun testVisibilityByOverlayable() {
+        assertThat(device.installJavaResourceApk(tempFolder, ACTOR_APK, false)).isNull()
+        assertThat(device.installJavaResourceApk(tempFolder, OVERLAY_APK, false)).isNull()
+        assertThat(device.installJavaResourceApk(tempFolder, TARGET_NO_OVERLAYABLE_APK, false))
+            .isNull()
+
+        runDeviceTests(
+            ACTOR_PKG_NAME, "$ACTOR_PKG_NAME.OverlayableVisibilityTest",
+            "verifyNotVisible"
+        )
+
+        assertThat(device.installJavaResourceApk(tempFolder, TARGET_APK, true)).isNull()
+
+        assertWithMessage(device.executeShellCommand("dumpsys package $OVERLAY_APK"))
+
+        runDeviceTests(
+            ACTOR_PKG_NAME, "$ACTOR_PKG_NAME.OverlayableVisibilityTest",
+            "verifyVisible"
+        )
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/Android.bp
new file mode 100644
index 0000000..92dcd34
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/Android.bp
@@ -0,0 +1,26 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestOverlay",
+    srcs: [
+        "src/**/*.kt",
+    ],
+}
diff --git a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/AndroidManifest.xml
similarity index 64%
copy from packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
copy to services/tests/PackageManagerServiceTests/host/test-apps/Overlay/AndroidManifest.xml
index b8ea622..21e4432 100644
--- a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/AndroidManifest.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
+<!-- Copyright (C) 2021 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -13,10 +13,10 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle" >
-    <solid
-        android:color="?android:attr/textColorSecondary" />
-    <corners android:radius="2dp" />
-</shape>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.overlay">
+    <application/>
+    <overlay android:targetPackage="com.android.server.pm.test.overlay.target"
+        android:targetName="Testing"/>
+</manifest>
diff --git a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/res/values/values.xml
similarity index 68%
copy from packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
copy to services/tests/PackageManagerServiceTests/host/test-apps/Overlay/res/values/values.xml
index b8ea622..f0b8586 100644
--- a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/res/values/values.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
+<!-- Copyright (C) 2021 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -13,10 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle" >
-    <solid
-        android:color="?android:attr/textColorSecondary" />
-    <corners android:radius="2dp" />
-</shape>
+
+<resources>
+    <string name="policy_public">You have been overlaid</string>
+</resources>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/Android.bp
new file mode 100644
index 0000000..5718474
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestOverlayActor",
+    srcs: ["src/**/*.kt"],
+    static_libs: [
+        "androidx.test.runner",
+        "junit",
+        "kotlin-test",
+        "truth-prebuilt",
+    ],
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/AndroidManifest.xml
new file mode 100644
index 0000000..a92a14f
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.overlay.actor"
+    >
+
+    <application/>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.server.pm.test.overlay.actor"
+        />
+
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/src/com/android/server/pm/test/overlay/actor/OverlayableVisibilityTest.kt b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/src/com/android/server/pm/test/overlay/actor/OverlayableVisibilityTest.kt
new file mode 100644
index 0000000..7537247f
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/src/com/android/server/pm/test/overlay/actor/OverlayableVisibilityTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.overlay.actor
+
+import android.content.Context
+import android.content.pm.PackageManager
+import androidx.test.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import kotlin.test.assertFailsWith
+
+class OverlayableVisibilityTest {
+
+    companion object {
+        private const val TARGET_PACKAGE = "com.android.server.pm.test.overlay.target"
+        private const val OVERLAY_PACKAGE = "com.android.server.pm.test.overlay"
+    }
+
+    private val context: Context = InstrumentationRegistry.getContext()
+    private val packageManager = context.packageManager
+
+    @Test
+    fun verifyVisible() {
+        assertThat(packageManager.getApplicationInfo(TARGET_PACKAGE, 0)).isNotNull()
+        assertThat(packageManager.getApplicationInfo(OVERLAY_PACKAGE, 0)).isNotNull()
+    }
+
+    @Test
+    fun verifyNotVisible() {
+        assertFailsWith(PackageManager.NameNotFoundException::class) {
+            packageManager.getApplicationInfo(TARGET_PACKAGE, 0)
+        }
+        assertFailsWith(PackageManager.NameNotFoundException::class) {
+            packageManager.getApplicationInfo(OVERLAY_PACKAGE, 0)
+        }
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/Android.bp
new file mode 100644
index 0000000..2bb6b82
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/Android.bp
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestOverlayTarget",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    resource_dirs: [
+        "res",
+        "res_overlayable",
+    ],
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestOverlayTargetNoOverlayable",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    resource_dirs: [
+        "res",
+    ],
+}
diff --git a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/AndroidManifest.xml
similarity index 68%
copy from packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
copy to services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/AndroidManifest.xml
index b8ea622..6038cb1 100644
--- a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/AndroidManifest.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
+<!-- Copyright (C) 2021 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -13,10 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle" >
-    <solid
-        android:color="?android:attr/textColorSecondary" />
-    <corners android:radius="2dp" />
-</shape>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.overlay.target">
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/assets/asset.txt b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/assets/asset.txt
new file mode 100644
index 0000000..4625e3b
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/assets/asset.txt
@@ -0,0 +1 @@
+Not overlaid
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/public.xml
similarity index 68%
copy from packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
copy to services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/public.xml
index b8ea622..283f5c1 100644
--- a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/public.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
+<!-- Copyright (C) 2021 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -13,10 +13,9 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle" >
-    <solid
-        android:color="?android:attr/textColorSecondary" />
-    <corners android:radius="2dp" />
-</shape>
+
+<resources>
+    <public-group type="string" first-id="0x7f010000">
+        <public name="policy_public" />
+    </public-group>
+</resources>
diff --git a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/values.xml
similarity index 68%
copy from packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
copy to services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/values.xml
index b8ea622..822194f 100644
--- a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/values.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
+<!-- Copyright (C) 2021 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -13,10 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle" >
-    <solid
-        android:color="?android:attr/textColorSecondary" />
-    <corners android:radius="2dp" />
-</shape>
+
+<resources>
+    <string name="policy_public">Not overlaid</string>
+</resources>
diff --git a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res_overlayable/values/overlayable.xml
similarity index 68%
rename from packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
rename to services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res_overlayable/values/overlayable.xml
index b8ea622..0100389 100644
--- a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res_overlayable/values/overlayable.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
+<!-- Copyright (C) 2021 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -13,10 +13,11 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle" >
-    <solid
-        android:color="?android:attr/textColorSecondary" />
-    <corners android:radius="2dp" />
-</shape>
+
+<resources>
+  <overlayable name="Testing" actor="overlay://androidTest/OverlayActorVisibilityTest">
+    <policy type="public">
+      <item type="string" name="policy_public" />
+    </policy>
+  </overlayable>
+</resources>
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
index dce853a..4de8d52 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
@@ -235,9 +235,23 @@
                     <category android:name="android.intent.category.BROWSABLE"/>
                     <category android:name="android.intent.category.DEFAULT"/>
                     <data android:scheme="https"/>
-                    <data android:path="/sub5"/>
-                    <data android:host="example5.com"/>
-                    <data android:host="invalid5"/>
+                    <data android:path="/sub6"/>
+                    <data android:host="example6.com"/>
+                    <data android:host="invalid6"/>
+                </intent-filter>
+                <intent-filter android:autoVerify="$autoVerify">
+                    <category android:name="android.intent.category.BROWSABLE"/>
+                    <category android:name="android.intent.category.DEFAULT"/>
+                    <data android:scheme="example7.com"/>
+                <intent-filter android:autoVerify="$autoVerify">
+                    <category android:name="android.intent.category.BROWSABLE"/>
+                    <category android:name="android.intent.category.DEFAULT"/>
+                    <data android:scheme="https"/>
+                </intent-filter>
+                <intent-filter android:autoVerify="$autoVerify">
+                    <category android:name="android.intent.category.BROWSABLE"/>
+                    <category android:name="android.intent.category.DEFAULT"/>
+                    <data android:path="/sub7"/>
                 </intent-filter>
             </xml>
         """.trimIndent()
@@ -324,6 +338,30 @@
                                     addDataAuthority("invalid6", null)
                                 }
                         )
+                        addIntent(
+                                ParsedIntentInfo().apply {
+                                    setAutoVerify(autoVerify)
+                                    addCategory(Intent.CATEGORY_BROWSABLE)
+                                    addCategory(Intent.CATEGORY_DEFAULT)
+                                    addDataAuthority("example7.com", null)
+                                }
+                        )
+                        addIntent(
+                                ParsedIntentInfo().apply {
+                                    setAutoVerify(autoVerify)
+                                    addCategory(Intent.CATEGORY_BROWSABLE)
+                                    addCategory(Intent.CATEGORY_DEFAULT)
+                                    addDataScheme("https")
+                                }
+                        )
+                        addIntent(
+                                ParsedIntentInfo().apply {
+                                    setAutoVerify(autoVerify)
+                                    addCategory(Intent.CATEGORY_BROWSABLE)
+                                    addCategory(Intent.CATEGORY_DEFAULT)
+                                    addDataPath("/sub7", PatternMatcher.PATTERN_LITERAL)
+                                }
+                        )
                     },
             )
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationValidIntentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationValidIntentTest.kt
new file mode 100644
index 0000000..98634b2
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationValidIntentTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.verify.domain
+
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.net.Uri
+import com.android.server.pm.verify.domain.DomainVerificationUtils
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class DomainVerificationValidIntentTest {
+
+    companion object {
+
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun parameters(): Array<Params> {
+            val succeeding = mutableListOf<Params>()
+            val failing = mutableListOf<Params>()
+
+            // Start with the base intent
+            val base = Params(categorySet = emptySet()).also { succeeding += it }
+
+            // Add all explicit supported categorySet
+            succeeding += base.copy(
+                categorySet = setOf(Intent.CATEGORY_BROWSABLE),
+                matchDefaultOnly = true
+            )
+
+            failing += base.copy(
+                categorySet = setOf(Intent.CATEGORY_BROWSABLE),
+                matchDefaultOnly = false
+            )
+
+            succeeding += listOf(true, false).map {
+                base.copy(
+                    categorySet = setOf(Intent.CATEGORY_DEFAULT),
+                    matchDefaultOnly = it
+                )
+            }
+
+            succeeding += listOf(true, false).map {
+                base.copy(
+                    categorySet = setOf(Intent.CATEGORY_BROWSABLE, Intent.CATEGORY_DEFAULT),
+                    matchDefaultOnly = it
+                )
+            }
+
+            // Fail on unsupported category
+            failing += listOf(
+                emptySet(),
+                setOf(Intent.CATEGORY_BROWSABLE),
+                setOf(Intent.CATEGORY_DEFAULT),
+                setOf(Intent.CATEGORY_BROWSABLE, Intent.CATEGORY_DEFAULT)
+            ).map { base.copy(categorySet = it + "invalid.CATEGORY") }
+
+            // Fail on unsupported action
+            failing += base.copy(action = Intent.ACTION_SEND)
+
+            // Fail on unsupported domain
+            failing += base.copy(domain = "invalid")
+
+            // Fail on empty domains
+            failing += base.copy(domain = "")
+
+            // Fail on missing scheme
+            failing += base.copy(
+                uriFunction = { Uri.Builder().authority("test.com").build() }
+            )
+
+            // Fail on missing host
+            failing += base.copy(
+                domain = "",
+                uriFunction = { Uri.Builder().scheme("https").build() }
+            )
+
+            succeeding.forEach { it.expected = true }
+            failing.forEach { it.expected = false }
+            return (succeeding + failing).toTypedArray()
+        }
+
+        data class Params(
+            val action: String = Intent.ACTION_VIEW,
+            val categorySet: Set<String> = mutableSetOf(),
+            val domain: String = "test.com",
+            val matchDefaultOnly: Boolean = true,
+            var expected: Boolean? = null,
+            val uriFunction: (domain: String) -> Uri = { Uri.parse("https://$it") }
+        ) {
+            val intent = Intent(action, uriFunction(domain)).apply {
+                categorySet.forEach(::addCategory)
+            }
+
+            override fun toString() = intent.toShortString(false, false, false, false) +
+                    ", matchDefaultOnly = $matchDefaultOnly, expected = $expected"
+        }
+    }
+
+    @Parameterized.Parameter(0)
+    lateinit var params: Params
+
+    @Test
+    fun verify() {
+        val flags = if (params.matchDefaultOnly) PackageManager.MATCH_DEFAULT_ONLY else 0
+        assertThat(DomainVerificationUtils.isDomainVerificationIntent(params.intent, flags))
+            .isEqualTo(params.expected)
+    }
+}
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index e4b650c..17a5dcc 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -28,6 +28,8 @@
     <uses-permission android:name="android.permission.MANAGE_APPOPS"/>
     <uses-permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"/>
     <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/>
+    <uses-permission
+        android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
 
     <!-- needed by MasterClearReceiverTest to display a system dialog -->
     <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/>
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 7654093..d55bbd1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -468,8 +468,9 @@
                 TEST_CALLING_UID);
     }
 
-    private void setPrioritizedAlarm(int type, long triggerTime, IAlarmListener listener) {
-        mService.setImpl(type, triggerTime, WINDOW_EXACT, 0, null, listener, "test",
+    private void setPrioritizedAlarm(int type, long triggerTime, long windowLength,
+            IAlarmListener listener) {
+        mService.setImpl(type, triggerTime, windowLength, 0, null, listener, "test",
                 FLAG_STANDALONE | FLAG_PRIORITIZE, null, null, TEST_CALLING_UID,
                 TEST_CALLING_PACKAGE, null);
     }
@@ -1685,7 +1686,7 @@
         final int numAlarms = 10;
         for (int i = 0; i < numAlarms; i++) {
             setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
-                    new IAlarmListener.Stub() {
+                    0, new IAlarmListener.Stub() {
                         @Override
                         public void doAlarm(IAlarmCompleteListener callback)
                                 throws RemoteException {
@@ -1720,7 +1721,7 @@
         final int numAlarms = 10;
         for (int i = 0; i < numAlarms; i++) {
             setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
-                    new IAlarmListener.Stub() {
+                    0, new IAlarmListener.Stub() {
                         @Override
                         public void doAlarm(IAlarmCompleteListener callback)
                                 throws RemoteException {
@@ -1738,12 +1739,12 @@
         }
         assertEquals(numAlarms, alarmsFired.get());
 
-        setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 3, new IAlarmListener.Stub() {
+        setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 3, 0, new IAlarmListener.Stub() {
             @Override
             public void doAlarm(IAlarmCompleteListener callback) throws RemoteException {
             }
         });
-        setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 2, new IAlarmListener.Stub() {
+        setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 2, 0, new IAlarmListener.Stub() {
             @Override
             public void doAlarm(IAlarmCompleteListener callback) throws RemoteException {
             }
@@ -2263,6 +2264,28 @@
     }
 
     @Test
+    public void minWindowPriorityAlarm() {
+        doReturn(true).when(
+                () -> CompatChanges.isChangeEnabled(
+                        eq(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS),
+                        anyString(), any(UserHandle.class)));
+        final long minWindow = 73;
+        setDeviceConfigLong(KEY_MIN_WINDOW, minWindow);
+
+        // 0 is WINDOW_EXACT and < 0 is WINDOW_HEURISTIC.
+        for (int window = 1; window <= minWindow; window++) {
+            setPrioritizedAlarm(ELAPSED_REALTIME, 0, window, new IAlarmListener.Stub() {
+                @Override
+                public void doAlarm(IAlarmCompleteListener callback) throws RemoteException {
+                }
+            });
+            assertEquals(1, mService.mAlarmStore.size());
+            final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0);
+            assertEquals(window, a.windowLength);
+        }
+    }
+
+    @Test
     public void denyListPackagesAdded() {
         mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{"p1", "p2", "p3"});
         setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "p2,p4,p5");
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
new file mode 100644
index 0000000..a8d8a90
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -0,0 +1,488 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.app;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.app.GameManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.function.Supplier;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class GameManagerServiceTests {
+    @Mock MockContext mMockContext;
+    private static final String TAG = "GameServiceTests";
+    private static final String PACKAGE_NAME_INVALID = "com.android.app";
+    private static final int USER_ID_1 = 1001;
+    private static final int USER_ID_2 = 1002;
+
+    private MockitoSession mMockingSession;
+    private String mPackageName;
+    @Mock
+    private PackageManager mMockPackageManager;
+
+    // Stolen from ConnectivityServiceTest.MockContext
+    class MockContext extends ContextWrapper {
+        private static final String TAG = "MockContext";
+
+        // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
+        private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();
+
+        MockContext(Context base) {
+            super(base);
+        }
+
+        /**
+         * Mock checks for the specified permission, and have them behave as per {@code granted}.
+         *
+         * <p>Passing null reverts to default behavior, which does a real permission check on the
+         * test package.
+         *
+         * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
+         *                {@link PackageManager#PERMISSION_DENIED}.
+         */
+        public void setPermission(String permission, Integer granted) {
+            mMockedPermissions.put(permission, granted);
+        }
+
+        private int checkMockedPermission(String permission, Supplier<Integer> ifAbsent) {
+            final Integer granted = mMockedPermissions.get(permission);
+            return granted != null ? granted : ifAbsent.get();
+        }
+
+        @Override
+        public int checkPermission(String permission, int pid, int uid) {
+            return checkMockedPermission(
+                    permission, () -> super.checkPermission(permission, pid, uid));
+        }
+
+        @Override
+        public int checkCallingOrSelfPermission(String permission) {
+            return checkMockedPermission(
+                    permission, () -> super.checkCallingOrSelfPermission(permission));
+        }
+
+        @Override
+        public void enforceCallingOrSelfPermission(String permission, String message) {
+            final Integer granted = mMockedPermissions.get(permission);
+            if (granted == null) {
+                super.enforceCallingOrSelfPermission(permission, message);
+                return;
+            }
+
+            if (!granted.equals(PackageManager.PERMISSION_GRANTED)) {
+                throw new SecurityException("[Test] permission denied: " + permission);
+            }
+        }
+
+        @Override
+        public PackageManager getPackageManager() {
+            return mMockPackageManager;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(DeviceConfig.class)
+                .strictness(Strictness.WARN)
+                .startMocking();
+        mMockContext = new MockContext(InstrumentationRegistry.getContext());
+        mPackageName = mMockContext.getPackageName();
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.category = ApplicationInfo.CATEGORY_GAME;
+        final PackageInfo pi = new PackageInfo();
+        pi.packageName = mPackageName;
+        final List<PackageInfo> packages = new ArrayList<>();
+        packages.add(pi);
+        when(mMockPackageManager.getInstalledPackages(anyInt())).thenReturn(packages);
+        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+                .thenReturn(applicationInfo);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+
+    private void mockModifyGameModeGranted() {
+        mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
+                PackageManager.PERMISSION_GRANTED);
+    }
+
+    private void mockModifyGameModeDenied() {
+        mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
+                PackageManager.PERMISSION_DENIED);
+    }
+
+    private void mockDeviceConfigDefault() {
+        DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder(
+                DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, "").build();
+        when(DeviceConfig.getProperties(anyString(), anyString()))
+                .thenReturn(properties);
+    }
+
+    private void mockDeviceConfigNone() {
+        DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder(
+                DeviceConfig.NAMESPACE_GAME_OVERLAY).build();
+        when(DeviceConfig.getProperties(anyString(), anyString()))
+                .thenReturn(properties);
+    }
+
+    private void mockDeviceConfigPerformance() {
+        String configString = "mode=2,downscaleFactor=0.5";
+        DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder(
+                DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build();
+        when(DeviceConfig.getProperties(anyString(), anyString()))
+                .thenReturn(properties);
+    }
+
+    private void mockDeviceConfigBattery() {
+        String configString = "mode=3,downscaleFactor=0.7";
+        DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder(
+                DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build();
+        when(DeviceConfig.getProperties(anyString(), anyString()))
+                .thenReturn(properties);
+    }
+
+    private void mockDeviceConfigAll() {
+        String configString = "mode=3,downscaleFactor=0.7:mode=2,downscaleFactor=0.5";
+        DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder(
+                DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build();
+        when(DeviceConfig.getProperties(anyString(), anyString()))
+                .thenReturn(properties);
+    }
+
+    private void mockDeviceConfigInvalid() {
+        String configString = "mode=2,downscaleFactor=0.55";
+        DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder(
+                DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build();
+        when(DeviceConfig.getProperties(anyString(), anyString()))
+                .thenReturn(properties);
+    }
+
+    private void mockDeviceConfigMalformed() {
+        String configString = "adsljckv=nin3rn9hn1231245:8795tq=21ewuydg";
+        DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder(
+                DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build();
+        when(DeviceConfig.getProperties(anyString(), anyString()))
+                .thenReturn(properties);
+    }
+
+    /**
+     * By default game mode is not supported.
+     */
+    @Test
+    public void testGameModeDefaultValue() {
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+
+        mockModifyGameModeGranted();
+
+        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+    }
+
+    /**
+     * Test the default behaviour for a nonexistent user.
+     */
+    @Test
+    public void testDefaultValueForNonexistentUser() {
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+
+        mockModifyGameModeGranted();
+
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_2);
+        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+                gameManagerService.getGameMode(mPackageName, USER_ID_2));
+    }
+
+    /**
+     * Test getter and setter of game modes.
+     */
+    @Test
+    public void testGameMode() {
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+
+        mockModifyGameModeGranted();
+
+        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_STANDARD,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
+                USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+    }
+
+    /**
+     * Test permission.MANAGE_GAME_MODE is checked
+     */
+    @Test
+    public void testGetGameModeInvalidPackageName() {
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+        try {
+            assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+                    gameManagerService.getGameMode(PACKAGE_NAME_INVALID,
+                            USER_ID_1));
+
+            fail("GameManagerService failed to generate SecurityException when "
+                    + "permission.MANAGE_GAME_MODE is not granted.");
+        } catch (SecurityException ignored) {
+        }
+
+        // The test should throw an exception, so the test is passing if we get here.
+    }
+
+    /**
+     * Test permission.MANAGE_GAME_MODE is checked
+     */
+    @Test
+    public void testSetGameModePermissionDenied() {
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+
+        // Update the game mode so we can read back something valid.
+        mockModifyGameModeGranted();
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_STANDARD,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+
+        // Deny permission.MANAGE_GAME_MODE and verify the game mode is not updated.
+        mockModifyGameModeDenied();
+        try {
+            gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
+                    USER_ID_1);
+
+            fail("GameManagerService failed to generate SecurityException when "
+                    + "permission.MANAGE_GAME_MODE is denied.");
+        } catch (SecurityException ignored) {
+        }
+
+        // The test should throw an exception, so the test is passing if we get here.
+        mockModifyGameModeGranted();
+        // Verify that the Game Mode value wasn't updated.
+        assertEquals(GameManager.GAME_MODE_STANDARD,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+    }
+
+    /**
+     * Test game modes are user-specific.
+     */
+    @Test
+    public void testGameModeMultipleUsers() {
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+        gameManagerService.onUserStarting(USER_ID_2);
+
+        mockModifyGameModeGranted();
+
+        // Set User 1 to Standard
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_STANDARD,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+
+        // Set User 2 to Performance and verify User 1 is still Standard
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
+                USER_ID_2);
+        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
+                gameManagerService.getGameMode(mPackageName, USER_ID_2));
+        assertEquals(GameManager.GAME_MODE_STANDARD,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+
+        // Set User 1 to Battery and verify User 2 is still Performance
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY,
+                USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_BATTERY,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
+                gameManagerService.getGameMode(mPackageName, USER_ID_2));
+    }
+
+    /**
+     * Phonesky device config exists, but is only propagating the default value.
+     */
+    @Test
+    public void testDeviceConfigDefault() {
+        mockDeviceConfigDefault();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+        gameManagerService.loadDeviceConfigLocked();
+
+        int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
+        assertEquals(modes.length, 1);
+        assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED);
+    }
+
+    /**
+     * Phonesky device config does not exists.
+     */
+    @Test
+    public void testDeviceConfigNone() {
+        mockDeviceConfigNone();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+        gameManagerService.loadDeviceConfigLocked();
+
+        int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
+        assertEquals(modes.length, 1);
+        assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED);
+    }
+
+    /**
+     * Phonesky device config for performance mode exists and is valid.
+     */
+    @Test
+    public void testDeviceConfigPerformance() {
+        mockDeviceConfigPerformance();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+        gameManagerService.loadDeviceConfigLocked();
+
+        boolean perfModeExists = false;
+        int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
+        for (int mode : modes) {
+            if (mode == GameManager.GAME_MODE_PERFORMANCE) {
+                perfModeExists = true;
+            }
+        }
+        assertEquals(modes.length, 1);
+        assertTrue(perfModeExists);
+    }
+
+    /**
+     * Phonesky device config for battery mode exists and is valid.
+     */
+    @Test
+    public void testDeviceConfigBattery() {
+        mockDeviceConfigBattery();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+        gameManagerService.loadDeviceConfigLocked();
+
+        boolean batteryModeExists = false;
+        int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
+        for (int mode : modes) {
+            if (mode == GameManager.GAME_MODE_BATTERY) {
+                batteryModeExists = true;
+            }
+        }
+        assertEquals(modes.length, 1);
+        assertTrue(batteryModeExists);
+    }
+
+    /**
+     * Phonesky device configs for both battery and performance modes exists and are valid.
+     */
+    @Test
+    public void testDeviceConfigAll() {
+        mockDeviceConfigAll();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+        gameManagerService.loadDeviceConfigLocked();
+
+        boolean batteryModeExists = false;
+        boolean perfModeExists = false;
+        int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
+        for (int mode : modes) {
+            if (mode == GameManager.GAME_MODE_BATTERY) {
+                batteryModeExists = true;
+            } else if (mode == GameManager.GAME_MODE_PERFORMANCE) {
+                perfModeExists = true;
+            }
+        }
+        assertTrue(batteryModeExists);
+        assertTrue(perfModeExists);
+    }
+
+    /**
+     * Phonesky device config contains values that parse correctly but are not valid in game mode.
+     */
+    @Test
+    public void testDeviceConfigInvalid() {
+        mockDeviceConfigInvalid();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+        gameManagerService.loadDeviceConfigLocked();
+
+        int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
+        assertEquals(modes.length, 1);
+        assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED);
+    }
+
+    /**
+     * Phonesky device config is garbage.
+     */
+    @Test
+    public void testDeviceConfigMalformed() {
+        mockDeviceConfigMalformed();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+        gameManagerService.loadDeviceConfigLocked();
+
+        int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
+        assertEquals(modes.length, 1);
+        assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
deleted file mode 100644
index edc0d46..0000000
--- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import android.Manifest;
-import android.app.GameManager;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.PackageManager;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-
-import java.util.HashMap;
-import java.util.function.Supplier;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class GameManagerServiceTests {
-
-    private static final String TAG = "GameServiceTests";
-    private static final String PACKAGE_NAME_INVALID = "com.android.app";
-    private static final int USER_ID_1 = 1001;
-    private static final int USER_ID_2 = 1002;
-
-    // Stolen from ConnectivityServiceTest.MockContext
-    static class MockContext extends ContextWrapper {
-        private static final String TAG = "MockContext";
-
-        // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
-        private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();
-
-        @Mock
-        private final MockPackageManager mMockPackageManager;
-
-        MockContext(Context base) {
-            super(base);
-            mMockPackageManager = new MockPackageManager();
-        }
-
-        /**
-         * Mock checks for the specified permission, and have them behave as per {@code granted}.
-         *
-         * <p>Passing null reverts to default behavior, which does a real permission check on the
-         * test package.
-         *
-         * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
-         *                {@link PackageManager#PERMISSION_DENIED}.
-         */
-        public void setPermission(String permission, Integer granted) {
-            mMockedPermissions.put(permission, granted);
-        }
-
-        private int checkMockedPermission(String permission, Supplier<Integer> ifAbsent) {
-            final Integer granted = mMockedPermissions.get(permission);
-            return granted != null ? granted : ifAbsent.get();
-        }
-
-        @Override
-        public int checkPermission(String permission, int pid, int uid) {
-            return checkMockedPermission(
-                    permission, () -> super.checkPermission(permission, pid, uid));
-        }
-
-        @Override
-        public int checkCallingOrSelfPermission(String permission) {
-            return checkMockedPermission(
-                    permission, () -> super.checkCallingOrSelfPermission(permission));
-        }
-
-        @Override
-        public void enforceCallingOrSelfPermission(String permission, String message) {
-            final Integer granted = mMockedPermissions.get(permission);
-            if (granted == null) {
-                super.enforceCallingOrSelfPermission(permission, message);
-                return;
-            }
-
-            if (!granted.equals(PackageManager.PERMISSION_GRANTED)) {
-                throw new SecurityException("[Test] permission denied: " + permission);
-            }
-        }
-
-        @Override
-        public PackageManager getPackageManager() {
-            return mMockPackageManager;
-        }
-    }
-
-    @Mock
-    private MockContext mMockContext;
-
-    private String mPackageName;
-
-    @Before
-    public void setUp() throws Exception {
-        mMockContext = new MockContext(InstrumentationRegistry.getContext());
-        mPackageName = mMockContext.getPackageName();
-    }
-
-    private void mockModifyGameModeGranted() {
-        mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
-                PackageManager.PERMISSION_GRANTED);
-    }
-
-    private void mockModifyGameModeDenied() {
-        mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
-                PackageManager.PERMISSION_DENIED);
-    }
-
-    /**
-     * By default game mode is not supported.
-     */
-    @Test
-    public void testGameModeDefaultValue() {
-        GameManagerService gameManagerService = new GameManagerService(mMockContext);
-        gameManagerService.onUserStarting(USER_ID_1);
-
-        mockModifyGameModeGranted();
-
-        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
-                gameManagerService.getGameMode(mPackageName, USER_ID_1));
-    }
-
-    /**
-     * Test the default behaviour for a nonexistent user.
-     */
-    @Test
-    public void testDefaultValueForNonexistentUser() {
-        GameManagerService gameManagerService = new GameManagerService(mMockContext);
-        gameManagerService.onUserStarting(USER_ID_1);
-
-        mockModifyGameModeGranted();
-
-        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_2);
-        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
-                gameManagerService.getGameMode(mPackageName, USER_ID_2));
-    }
-
-    /**
-     * Test getter and setter of game modes.
-     */
-    @Test
-    public void testGameMode() {
-        GameManagerService gameManagerService = new GameManagerService(mMockContext);
-        gameManagerService.onUserStarting(USER_ID_1);
-
-        mockModifyGameModeGranted();
-
-        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
-                gameManagerService.getGameMode(mPackageName, USER_ID_1));
-        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
-        assertEquals(GameManager.GAME_MODE_STANDARD,
-                gameManagerService.getGameMode(mPackageName, USER_ID_1));
-        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
-                USER_ID_1);
-        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
-                gameManagerService.getGameMode(mPackageName, USER_ID_1));
-    }
-
-    /**
-     * Test permission.MANAGE_GAME_MODE is checked
-     */
-    @Test
-    public void testGetGameModeInvalidPackageName() {
-        GameManagerService gameManagerService = new GameManagerService(mMockContext);
-        gameManagerService.onUserStarting(USER_ID_1);
-
-        try {
-            assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
-                    gameManagerService.getGameMode(PACKAGE_NAME_INVALID,
-                            USER_ID_1));
-
-            fail("GameManagerService failed to generate SecurityException when "
-                    + "permission.MANAGE_GAME_MODE is not granted.");
-        } catch (SecurityException ignored) {
-        }
-
-        // The test should throw an exception, so the test is passing if we get here.
-    }
-
-    /**
-     * Test permission.MANAGE_GAME_MODE is checked
-     */
-    @Test
-    public void testSetGameModePermissionDenied() {
-        GameManagerService gameManagerService = new GameManagerService(mMockContext);
-        gameManagerService.onUserStarting(USER_ID_1);
-
-        // Update the game mode so we can read back something valid.
-        mockModifyGameModeGranted();
-        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
-        assertEquals(GameManager.GAME_MODE_STANDARD,
-                gameManagerService.getGameMode(mPackageName, USER_ID_1));
-
-        // Deny permission.MANAGE_GAME_MODE and verify the game mode is not updated.
-        mockModifyGameModeDenied();
-        try {
-            gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
-                    USER_ID_1);
-
-            fail("GameManagerService failed to generate SecurityException when "
-                    + "permission.MANAGE_GAME_MODE is denied.");
-        } catch (SecurityException ignored) {
-        }
-
-        // The test should throw an exception, so the test is passing if we get here.
-        mockModifyGameModeGranted();
-        // Verify that the Game Mode value wasn't updated.
-        assertEquals(GameManager.GAME_MODE_STANDARD,
-                gameManagerService.getGameMode(mPackageName, USER_ID_1));
-    }
-
-    /**
-     * Test game modes are user-specific.
-     */
-    @Test
-    public void testGameModeMultipleUsers() {
-        GameManagerService gameManagerService = new GameManagerService(mMockContext);
-        gameManagerService.onUserStarting(USER_ID_1);
-        gameManagerService.onUserStarting(USER_ID_2);
-
-        mockModifyGameModeGranted();
-
-        // Set User 1 to Standard
-        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
-        assertEquals(GameManager.GAME_MODE_STANDARD,
-                gameManagerService.getGameMode(mPackageName, USER_ID_1));
-
-        // Set User 2 to Performance and verify User 1 is still Standard
-        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
-                USER_ID_2);
-        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
-                gameManagerService.getGameMode(mPackageName, USER_ID_2));
-        assertEquals(GameManager.GAME_MODE_STANDARD,
-                gameManagerService.getGameMode(mPackageName, USER_ID_1));
-
-        // Set User 1 to Battery and verify User 2 is still Performance
-        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY,
-                USER_ID_1);
-        assertEquals(GameManager.GAME_MODE_BATTERY,
-                gameManagerService.getGameMode(mPackageName, USER_ID_1));
-        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
-                gameManagerService.getGameMode(mPackageName, USER_ID_2));
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
index 4240581..b552fd5 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
@@ -32,6 +32,8 @@
 
 import androidx.test.core.app.ApplicationProvider;
 
+import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
+
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
@@ -203,7 +205,7 @@
         mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
 
         // Set schema1
-        String prefix = AppSearchImpl.createPrefix("package", "database");
+        String prefix = PrefixUtil.createPrefix("package", "database");
         mAppSearchImpl.setSchema(
                 "package",
                 "database",
@@ -280,7 +282,7 @@
         mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
         mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
 
-        String prefix = AppSearchImpl.createPrefix("package", "database");
+        String prefix = PrefixUtil.createPrefix("package", "database");
         mAppSearchImpl.setSchema(
                 "package",
                 "database",
@@ -353,7 +355,7 @@
 
     @Test
     public void testSetSchema_defaultPlatformVisible() throws Exception {
-        String prefix = AppSearchImpl.createPrefix("package", "database");
+        String prefix = PrefixUtil.createPrefix("package", "database");
         mAppSearchImpl.setSchema(
                 "package",
                 "database",
@@ -372,7 +374,7 @@
 
     @Test
     public void testSetSchema_platformHidden() throws Exception {
-        String prefix = AppSearchImpl.createPrefix("package", "database");
+        String prefix = PrefixUtil.createPrefix("package", "database");
         mAppSearchImpl.setSchema(
                 "package",
                 "database",
@@ -391,7 +393,7 @@
 
     @Test
     public void testSetSchema_defaultNotPackageAccessible() throws Exception {
-        String prefix = AppSearchImpl.createPrefix("package", "database");
+        String prefix = PrefixUtil.createPrefix("package", "database");
         mAppSearchImpl.setSchema(
                 "package",
                 "database",
@@ -419,7 +421,7 @@
         mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
         mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
 
-        String prefix = AppSearchImpl.createPrefix("package", "database");
+        String prefix = PrefixUtil.createPrefix("package", "database");
         mAppSearchImpl.setSchema(
                 "package",
                 "database",
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java b/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java
index 8d35ebe..11ae76b 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java
@@ -28,6 +28,8 @@
 
 import androidx.test.core.app.ApplicationProvider;
 
+import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
+
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -78,13 +80,9 @@
     @Test
     public void testValidPackageName() {
         assertThat(VisibilityStore.PACKAGE_NAME)
-                .doesNotContain(
-                        "" + AppSearchImpl.PACKAGE_DELIMITER); // Convert the chars to CharSequences
+                .doesNotContain(String.valueOf(PrefixUtil.PACKAGE_DELIMITER));
         assertThat(VisibilityStore.PACKAGE_NAME)
-                .doesNotContain(
-                        ""
-                                + AppSearchImpl
-                                        .DATABASE_DELIMITER); // Convert the chars to CharSequences
+                .doesNotContain(String.valueOf(PrefixUtil.DATABASE_DELIMITER));
     }
 
     /**
@@ -93,13 +91,9 @@
     @Test
     public void testValidDatabaseName() {
         assertThat(VisibilityStore.DATABASE_NAME)
-                .doesNotContain(
-                        "" + AppSearchImpl.PACKAGE_DELIMITER); // Convert the chars to CharSequences
+                .doesNotContain(String.valueOf(PrefixUtil.PACKAGE_DELIMITER));
         assertThat(VisibilityStore.DATABASE_NAME)
-                .doesNotContain(
-                        ""
-                                + AppSearchImpl
-                                        .DATABASE_DELIMITER); // Convert the chars to CharSequences
+                .doesNotContain(String.valueOf(PrefixUtil.DATABASE_DELIMITER));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
index ba4d585..380d9be 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
@@ -16,6 +16,10 @@
 
 package com.android.server.appsearch.external.localstorage;
 
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.addPrefixToDocument;
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.createPrefix;
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.removePrefixesFromDocument;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.testng.Assert.expectThrows;
@@ -35,8 +39,10 @@
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.server.appsearch.external.localstorage.converter.GenericDocumentToProtoConverter;
+import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
 import com.android.server.appsearch.proto.DocumentProto;
 import com.android.server.appsearch.proto.GetOptimizeInfoResultProto;
+import com.android.server.appsearch.proto.PersistType;
 import com.android.server.appsearch.proto.PropertyConfigProto;
 import com.android.server.appsearch.proto.PropertyProto;
 import com.android.server.appsearch.proto.SchemaProto;
@@ -47,6 +53,7 @@
 import com.android.server.appsearch.proto.TermMatchType;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 
 import org.junit.Before;
@@ -54,6 +61,7 @@
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -96,56 +104,71 @@
         // Create a copy so we can modify it.
         List<SchemaTypeConfigProto> existingTypes =
                 new ArrayList<>(existingSchemaBuilder.getTypesList());
-
-        SchemaProto newSchema =
-                SchemaProto.newBuilder()
-                        .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Foo").build())
-                        .addTypes(
-                                SchemaTypeConfigProto.newBuilder()
-                                        .setSchemaType("TestType")
-                                        .addProperties(
-                                                PropertyConfigProto.newBuilder()
-                                                        .setPropertyName("subject")
-                                                        .setDataType(
-                                                                PropertyConfigProto.DataType.Code
-                                                                        .STRING)
-                                                        .setCardinality(
-                                                                PropertyConfigProto.Cardinality.Code
-                                                                        .OPTIONAL)
-                                                        .setStringIndexingConfig(
-                                                                StringIndexingConfig.newBuilder()
-                                                                        .setTokenizerType(
-                                                                                StringIndexingConfig
-                                                                                        .TokenizerType
-                                                                                        .Code.PLAIN)
-                                                                        .setTermMatchType(
-                                                                                TermMatchType.Code
-                                                                                        .PREFIX)
-                                                                        .build())
-                                                        .build())
-                                        .addProperties(
-                                                PropertyConfigProto.newBuilder()
-                                                        .setPropertyName("link")
-                                                        .setDataType(
-                                                                PropertyConfigProto.DataType.Code
-                                                                        .DOCUMENT)
-                                                        .setCardinality(
-                                                                PropertyConfigProto.Cardinality.Code
-                                                                        .OPTIONAL)
-                                                        .setSchemaType("RefType")
+        SchemaTypeConfigProto schemaTypeConfigProto1 =
+                SchemaTypeConfigProto.newBuilder().setSchemaType("Foo").build();
+        SchemaTypeConfigProto schemaTypeConfigProto2 =
+                SchemaTypeConfigProto.newBuilder()
+                        .setSchemaType("TestType")
+                        .addProperties(
+                                PropertyConfigProto.newBuilder()
+                                        .setPropertyName("subject")
+                                        .setDataType(PropertyConfigProto.DataType.Code.STRING)
+                                        .setCardinality(
+                                                PropertyConfigProto.Cardinality.Code.OPTIONAL)
+                                        .setStringIndexingConfig(
+                                                StringIndexingConfig.newBuilder()
+                                                        .setTokenizerType(
+                                                                StringIndexingConfig.TokenizerType
+                                                                        .Code.PLAIN)
+                                                        .setTermMatchType(TermMatchType.Code.PREFIX)
                                                         .build())
                                         .build())
+                        .addProperties(
+                                PropertyConfigProto.newBuilder()
+                                        .setPropertyName("link")
+                                        .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
+                                        .setCardinality(
+                                                PropertyConfigProto.Cardinality.Code.OPTIONAL)
+                                        .setSchemaType("RefType")
+                                        .build())
+                        .build();
+        SchemaTypeConfigProto schemaTypeConfigProto3 =
+                SchemaTypeConfigProto.newBuilder().setSchemaType("RefType").build();
+        SchemaProto newSchema =
+                SchemaProto.newBuilder()
+                        .addTypes(schemaTypeConfigProto1)
+                        .addTypes(schemaTypeConfigProto2)
+                        .addTypes(schemaTypeConfigProto3)
                         .build();
 
         AppSearchImpl.RewrittenSchemaResults rewrittenSchemaResults =
                 mAppSearchImpl.rewriteSchema(
-                        AppSearchImpl.createPrefix("package", "newDatabase"),
-                        existingSchemaBuilder,
-                        newSchema);
+                        createPrefix("package", "newDatabase"), existingSchemaBuilder, newSchema);
 
         // We rewrote all the new types that were added. And nothing was removed.
-        assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes)
-                .containsExactly("package$newDatabase/Foo", "package$newDatabase/TestType");
+        assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes.keySet())
+                .containsExactly(
+                        "package$newDatabase/Foo",
+                        "package$newDatabase/TestType",
+                        "package$newDatabase/RefType");
+        assertThat(
+                        rewrittenSchemaResults
+                                .mRewrittenPrefixedTypes
+                                .get("package$newDatabase/Foo")
+                                .getSchemaType())
+                .isEqualTo("package$newDatabase/Foo");
+        assertThat(
+                        rewrittenSchemaResults
+                                .mRewrittenPrefixedTypes
+                                .get("package$newDatabase/TestType")
+                                .getSchemaType())
+                .isEqualTo("package$newDatabase/TestType");
+        assertThat(
+                        rewrittenSchemaResults
+                                .mRewrittenPrefixedTypes
+                                .get("package$newDatabase/RefType")
+                                .getSchemaType())
+                .isEqualTo("package$newDatabase/RefType");
         assertThat(rewrittenSchemaResults.mDeletedPrefixedTypes).isEmpty();
 
         SchemaProto expectedSchema =
@@ -190,6 +213,10 @@
                                                                 "package$newDatabase/RefType")
                                                         .build())
                                         .build())
+                        .addTypes(
+                                SchemaTypeConfigProto.newBuilder()
+                                        .setSchemaType("package$newDatabase/RefType")
+                                        .build())
                         .build();
 
         existingTypes.addAll(expectedSchema.getTypesList());
@@ -216,12 +243,12 @@
 
         AppSearchImpl.RewrittenSchemaResults rewrittenSchemaResults =
                 mAppSearchImpl.rewriteSchema(
-                        AppSearchImpl.createPrefix("package", "existingDatabase"),
+                        createPrefix("package", "existingDatabase"),
                         existingSchemaBuilder,
                         newSchema);
 
         // Nothing was removed, but the method did rewrite the type name.
-        assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes)
+        assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes.keySet())
                 .containsExactly("package$existingDatabase/Foo");
         assertThat(rewrittenSchemaResults.mDeletedPrefixedTypes).isEmpty();
 
@@ -251,14 +278,15 @@
 
         AppSearchImpl.RewrittenSchemaResults rewrittenSchemaResults =
                 mAppSearchImpl.rewriteSchema(
-                        AppSearchImpl.createPrefix("package", "existingDatabase"),
+                        createPrefix("package", "existingDatabase"),
                         existingSchemaBuilder,
                         newSchema);
 
         // Bar type was rewritten, but Foo ended up being deleted since it wasn't included in the
         // new schema.
         assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes)
-                .containsExactly("package$existingDatabase/Bar");
+                .containsKey("package$existingDatabase/Bar");
+        assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes.keySet().size()).isEqualTo(1);
         assertThat(rewrittenSchemaResults.mDeletedPrefixedTypes)
                 .containsExactly("package$existingDatabase/Foo");
 
@@ -308,8 +336,7 @@
                         .build();
 
         DocumentProto.Builder actualDocument = documentProto.toBuilder();
-        mAppSearchImpl.addPrefixToDocument(
-                actualDocument, AppSearchImpl.createPrefix("package", "databaseName"));
+        addPrefixToDocument(actualDocument, createPrefix("package", "databaseName"));
         assertThat(actualDocument.build()).isEqualTo(expectedDocumentProto);
     }
 
@@ -347,8 +374,7 @@
                         .build();
 
         DocumentProto.Builder actualDocument = documentProto.toBuilder();
-        assertThat(mAppSearchImpl.removePrefixesFromDocument(actualDocument))
-                .isEqualTo("package$databaseName/");
+        assertThat(removePrefixesFromDocument(actualDocument)).isEqualTo("package$databaseName/");
         assertThat(actualDocument.build()).isEqualTo(expectedDocumentProto);
     }
 
@@ -365,8 +391,7 @@
         DocumentProto.Builder actualDocument = documentProto.toBuilder();
         AppSearchException e =
                 expectThrows(
-                        AppSearchException.class,
-                        () -> mAppSearchImpl.removePrefixesFromDocument(actualDocument));
+                        AppSearchException.class, () -> removePrefixesFromDocument(actualDocument));
         assertThat(e).hasMessageThat().contains("Found unexpected multiple prefix names");
     }
 
@@ -391,8 +416,7 @@
         DocumentProto.Builder actualDocument = documentProto.toBuilder();
         AppSearchException e =
                 expectThrows(
-                        AppSearchException.class,
-                        () -> mAppSearchImpl.removePrefixesFromDocument(actualDocument));
+                        AppSearchException.class, () -> removePrefixesFromDocument(actualDocument));
         assertThat(e).hasMessageThat().contains("Found unexpected multiple prefix names");
     }
 
@@ -484,7 +508,7 @@
         // Rewrite SearchSpec
         mAppSearchImpl.rewriteSearchSpecForPrefixesLocked(
                 searchSpecProto,
-                Collections.singleton(AppSearchImpl.createPrefix("package", "database")),
+                Collections.singleton(createPrefix("package", "database")),
                 ImmutableSet.of("package$database/type"));
         assertThat(searchSpecProto.getSchemaTypeFiltersList())
                 .containsExactly("package$database/type");
@@ -531,8 +555,7 @@
         mAppSearchImpl.rewriteSearchSpecForPrefixesLocked(
                 searchSpecProto,
                 ImmutableSet.of(
-                        AppSearchImpl.createPrefix("package", "database1"),
-                        AppSearchImpl.createPrefix("package", "database2")),
+                        createPrefix("package", "database1"), createPrefix("package", "database2")),
                 ImmutableSet.of(
                         "package$database1/typeA", "package$database1/typeB",
                         "package$database2/typeA", "package$database2/typeB"));
@@ -573,8 +596,7 @@
         assertThat(
                         mAppSearchImpl.rewriteSearchSpecForPrefixesLocked(
                                 searchSpecProto,
-                                Collections.singleton(
-                                        AppSearchImpl.createPrefix("package", "database")),
+                                Collections.singleton(createPrefix("package", "database")),
                                 /*allowedPrefixedSchemas=*/ Collections.emptySet()))
                 .isFalse();
     }
@@ -1082,7 +1104,7 @@
 
         // Has database1
         Set<String> expectedPrefixes = new ArraySet<>(existingPrefixes);
-        expectedPrefixes.add(AppSearchImpl.createPrefix("package", "database1"));
+        expectedPrefixes.add(createPrefix("package", "database1"));
         mAppSearchImpl.setSchema(
                 "package",
                 "database1",
@@ -1094,7 +1116,7 @@
         assertThat(mAppSearchImpl.getPrefixesLocked()).containsExactlyElementsIn(expectedPrefixes);
 
         // Has both databases
-        expectedPrefixes.add(AppSearchImpl.createPrefix("package", "database2"));
+        expectedPrefixes.add(createPrefix("package", "database2"));
         mAppSearchImpl.setSchema(
                 "package",
                 "database2",
@@ -1110,9 +1132,9 @@
     public void testRewriteSearchResultProto() throws Exception {
         final String prefix =
                 "com.package.foo"
-                        + AppSearchImpl.PACKAGE_DELIMITER
+                        + PrefixUtil.PACKAGE_DELIMITER
                         + "databaseName"
-                        + AppSearchImpl.DATABASE_DELIMITER;
+                        + PrefixUtil.DATABASE_DELIMITER;
         final String uri = "uri";
         final String namespace = prefix + "namespace";
         final String schemaType = prefix + "schema";
@@ -1128,18 +1150,22 @@
                 SearchResultProto.ResultProto.newBuilder().setDocument(documentProto).build();
         SearchResultProto searchResultProto =
                 SearchResultProto.newBuilder().addResults(resultProto).build();
+        SchemaTypeConfigProto schemaTypeConfigProto =
+                SchemaTypeConfigProto.newBuilder().setSchemaType(schemaType).build();
+        Map<String, Map<String, SchemaTypeConfigProto>> schemaMap =
+                ImmutableMap.of(prefix, ImmutableMap.of(schemaType, schemaTypeConfigProto));
 
         DocumentProto.Builder strippedDocumentProto = documentProto.toBuilder();
-        AppSearchImpl.removePrefixesFromDocument(strippedDocumentProto);
+        removePrefixesFromDocument(strippedDocumentProto);
         SearchResultPage searchResultPage =
-                AppSearchImpl.rewriteSearchResultProto(searchResultProto);
+                AppSearchImpl.rewriteSearchResultProto(searchResultProto, schemaMap);
         for (SearchResult result : searchResultPage.getResults()) {
             assertThat(result.getPackageName()).isEqualTo("com.package.foo");
             assertThat(result.getDatabaseName()).isEqualTo("databaseName");
             assertThat(result.getGenericDocument())
                     .isEqualTo(
                             GenericDocumentToProtoConverter.toGenericDocument(
-                                    strippedDocumentProto.build()));
+                                    strippedDocumentProto.build(), prefix, schemaMap.get(prefix)));
         }
     }
 
@@ -1609,7 +1635,221 @@
         expectThrows(
                 IllegalStateException.class,
                 () -> {
-                    appSearchImpl.persistToDisk();
+                    appSearchImpl.persistToDisk(PersistType.Code.FULL);
                 });
     }
+
+    @Test
+    public void testPutPersistsWithLiteFlush() throws Exception {
+        // Setup the index
+        Context context = ApplicationProvider.getApplicationContext();
+        File appsearchDir = mTemporaryFolder.newFolder();
+        AppSearchImpl appSearchImpl =
+                AppSearchImpl.create(
+                        appsearchDir,
+                        context,
+                        VisibilityStore.NO_OP_USER_ID,
+                        /*globalQuerierPackage=*/ "");
+
+        List<AppSearchSchema> schemas =
+                Collections.singletonList(new AppSearchSchema.Builder("type").build());
+        appSearchImpl.setSchema(
+                "package",
+                "database",
+                schemas,
+                /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
+                /*schemasPackageAccessible=*/ Collections.emptyMap(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
+
+        // Add a document and persist it.
+        GenericDocument document =
+                new GenericDocument.Builder<>("namespace1", "uri1", "type").build();
+        appSearchImpl.putDocument("package", "database", document, /*logger=*/ null);
+        appSearchImpl.persistToDisk(PersistType.Code.LITE);
+
+        GenericDocument getResult =
+                appSearchImpl.getDocument(
+                        "package", "database", "namespace1", "uri1", Collections.emptyMap());
+        assertThat(getResult).isEqualTo(document);
+
+        // That document should be visible even from another instance.
+        AppSearchImpl appSearchImpl2 =
+                AppSearchImpl.create(
+                        appsearchDir,
+                        context,
+                        VisibilityStore.NO_OP_USER_ID,
+                        /*globalQuerierPackage=*/ "");
+        getResult =
+                appSearchImpl2.getDocument(
+                        "package", "database", "namespace1", "uri1", Collections.emptyMap());
+        assertThat(getResult).isEqualTo(document);
+    }
+
+    @Test
+    public void testDeletePersistsWithLiteFlush() throws Exception {
+        // Setup the index
+        Context context = ApplicationProvider.getApplicationContext();
+        File appsearchDir = mTemporaryFolder.newFolder();
+        AppSearchImpl appSearchImpl =
+                AppSearchImpl.create(
+                        appsearchDir,
+                        context,
+                        VisibilityStore.NO_OP_USER_ID,
+                        /*globalQuerierPackage=*/ "");
+
+        List<AppSearchSchema> schemas =
+                Collections.singletonList(new AppSearchSchema.Builder("type").build());
+        appSearchImpl.setSchema(
+                "package",
+                "database",
+                schemas,
+                /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
+                /*schemasPackageAccessible=*/ Collections.emptyMap(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
+
+        // Add two documents and persist them.
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("namespace1", "uri1", "type").build();
+        appSearchImpl.putDocument("package", "database", document1, /*logger=*/ null);
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("namespace1", "uri2", "type").build();
+        appSearchImpl.putDocument("package", "database", document2, /*logger=*/ null);
+        appSearchImpl.persistToDisk(PersistType.Code.LITE);
+
+        GenericDocument getResult =
+                appSearchImpl.getDocument(
+                        "package", "database", "namespace1", "uri1", Collections.emptyMap());
+        assertThat(getResult).isEqualTo(document1);
+        getResult =
+                appSearchImpl.getDocument(
+                        "package", "database", "namespace1", "uri2", Collections.emptyMap());
+        assertThat(getResult).isEqualTo(document2);
+
+        // Delete the first document
+        appSearchImpl.remove("package", "database", "namespace1", "uri1");
+        appSearchImpl.persistToDisk(PersistType.Code.LITE);
+        expectThrows(
+                AppSearchException.class,
+                () ->
+                        appSearchImpl.getDocument(
+                                "package",
+                                "database",
+                                "namespace1",
+                                "uri1",
+                                Collections.emptyMap()));
+        getResult =
+                appSearchImpl.getDocument(
+                        "package", "database", "namespace1", "uri2", Collections.emptyMap());
+        assertThat(getResult).isEqualTo(document2);
+
+        // Only the second document should be retrievable from another instance.
+        AppSearchImpl appSearchImpl2 =
+                AppSearchImpl.create(
+                        appsearchDir,
+                        context,
+                        VisibilityStore.NO_OP_USER_ID,
+                        /*globalQuerierPackage=*/ "");
+        expectThrows(
+                AppSearchException.class,
+                () ->
+                        appSearchImpl2.getDocument(
+                                "package",
+                                "database",
+                                "namespace1",
+                                "uri1",
+                                Collections.emptyMap()));
+        getResult =
+                appSearchImpl2.getDocument(
+                        "package", "database", "namespace1", "uri2", Collections.emptyMap());
+        assertThat(getResult).isEqualTo(document2);
+    }
+
+    @Test
+    public void testDeleteByQueryPersistsWithLiteFlush() throws Exception {
+        // Setup the index
+        Context context = ApplicationProvider.getApplicationContext();
+        File appsearchDir = mTemporaryFolder.newFolder();
+        AppSearchImpl appSearchImpl =
+                AppSearchImpl.create(
+                        appsearchDir,
+                        context,
+                        VisibilityStore.NO_OP_USER_ID,
+                        /*globalQuerierPackage=*/ "");
+
+        List<AppSearchSchema> schemas =
+                Collections.singletonList(new AppSearchSchema.Builder("type").build());
+        appSearchImpl.setSchema(
+                "package",
+                "database",
+                schemas,
+                /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
+                /*schemasPackageAccessible=*/ Collections.emptyMap(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
+
+        // Add two documents and persist them.
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("namespace1", "uri1", "type").build();
+        appSearchImpl.putDocument("package", "database", document1, /*logger=*/ null);
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("namespace2", "uri2", "type").build();
+        appSearchImpl.putDocument("package", "database", document2, /*logger=*/ null);
+        appSearchImpl.persistToDisk(PersistType.Code.LITE);
+
+        GenericDocument getResult =
+                appSearchImpl.getDocument(
+                        "package", "database", "namespace1", "uri1", Collections.emptyMap());
+        assertThat(getResult).isEqualTo(document1);
+        getResult =
+                appSearchImpl.getDocument(
+                        "package", "database", "namespace2", "uri2", Collections.emptyMap());
+        assertThat(getResult).isEqualTo(document2);
+
+        // Delete the first document
+        appSearchImpl.removeByQuery(
+                "package",
+                "database",
+                "",
+                new SearchSpec.Builder()
+                        .addFilterNamespaces("namespace1")
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .build());
+        appSearchImpl.persistToDisk(PersistType.Code.LITE);
+        expectThrows(
+                AppSearchException.class,
+                () ->
+                        appSearchImpl.getDocument(
+                                "package",
+                                "database",
+                                "namespace1",
+                                "uri1",
+                                Collections.emptyMap()));
+        getResult =
+                appSearchImpl.getDocument(
+                        "package", "database", "namespace2", "uri2", Collections.emptyMap());
+        assertThat(getResult).isEqualTo(document2);
+
+        // Only the second document should be retrievable from another instance.
+        AppSearchImpl appSearchImpl2 =
+                AppSearchImpl.create(
+                        appsearchDir,
+                        context,
+                        VisibilityStore.NO_OP_USER_ID,
+                        /*globalQuerierPackage=*/ "");
+        expectThrows(
+                AppSearchException.class,
+                () ->
+                        appSearchImpl2.getDocument(
+                                "package",
+                                "database",
+                                "namespace1",
+                                "uri1",
+                                Collections.emptyMap()));
+        getResult =
+                appSearchImpl2.getDocument(
+                        "package", "database", "namespace2", "uri2", Collections.emptyMap());
+        assertThat(getResult).isEqualTo(document2);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverterTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverterTest.java
index 70e1e05..63f031722 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverterTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverterTest.java
@@ -21,35 +21,50 @@
 import android.app.appsearch.GenericDocument;
 
 import com.android.server.appsearch.proto.DocumentProto;
+import com.android.server.appsearch.proto.PropertyConfigProto;
 import com.android.server.appsearch.proto.PropertyProto;
+import com.android.server.appsearch.proto.SchemaTypeConfigProto;
 import com.android.server.appsearch.protobuf.ByteString;
 
+import com.google.common.collect.ImmutableMap;
+
 import org.junit.Test;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 public class GenericDocumentToProtoConverterTest {
     private static final byte[] BYTE_ARRAY_1 = new byte[] {(byte) 1, (byte) 2, (byte) 3};
     private static final byte[] BYTE_ARRAY_2 = new byte[] {(byte) 4, (byte) 5, (byte) 6, (byte) 7};
+    private static final String SCHEMA_TYPE_1 = "sDocumentPropertiesSchemaType1";
+    private static final String SCHEMA_TYPE_2 = "sDocumentPropertiesSchemaType2";
     private static final GenericDocument DOCUMENT_PROPERTIES_1 =
             new GenericDocument.Builder<GenericDocument.Builder<?>>(
-                            "namespace", "sDocumentProperties1", "sDocumentPropertiesSchemaType1")
+                            "namespace", "sDocumentProperties1", SCHEMA_TYPE_1)
                     .setCreationTimestampMillis(12345L)
                     .build();
     private static final GenericDocument DOCUMENT_PROPERTIES_2 =
             new GenericDocument.Builder<GenericDocument.Builder<?>>(
-                            "namespace", "sDocumentProperties2", "sDocumentPropertiesSchemaType2")
+                            "namespace", "sDocumentProperties2", SCHEMA_TYPE_2)
                     .setCreationTimestampMillis(6789L)
                     .build();
+    private static final SchemaTypeConfigProto SCHEMA_PROTO_1 =
+            SchemaTypeConfigProto.newBuilder().setSchemaType(SCHEMA_TYPE_1).build();
+    private static final SchemaTypeConfigProto SCHEMA_PROTO_2 =
+            SchemaTypeConfigProto.newBuilder().setSchemaType(SCHEMA_TYPE_2).build();
+    private static final String PREFIX = "package$databaseName/";
+    private static final Map<String, SchemaTypeConfigProto> SCHEMA_MAP =
+            ImmutableMap.of(
+                    PREFIX + SCHEMA_TYPE_1, SCHEMA_PROTO_1, PREFIX + SCHEMA_TYPE_2, SCHEMA_PROTO_2);
 
     @Test
     public void testDocumentProtoConvert() {
         GenericDocument document =
                 new GenericDocument.Builder<GenericDocument.Builder<?>>(
-                                "namespace", "uri1", "schemaType1")
+                                "namespace", "uri1", SCHEMA_TYPE_1)
                         .setCreationTimestampMillis(5L)
                         .setScore(1)
                         .setTtlMillis(1L)
@@ -66,7 +81,7 @@
         DocumentProto.Builder documentProtoBuilder =
                 DocumentProto.newBuilder()
                         .setUri("uri1")
-                        .setSchema("schemaType1")
+                        .setSchema(SCHEMA_TYPE_1)
                         .setCreationTimestampMs(5L)
                         .setScore(1)
                         .setTtlMs(1L)
@@ -109,9 +124,133 @@
             documentProtoBuilder.addProperties(propertyProtoMap.get(key));
         }
         DocumentProto documentProto = documentProtoBuilder.build();
-        assertThat(GenericDocumentToProtoConverter.toDocumentProto(document))
-                .isEqualTo(documentProto);
-        assertThat(document)
-                .isEqualTo(GenericDocumentToProtoConverter.toGenericDocument(documentProto));
+
+        GenericDocument convertedGenericDocument =
+                GenericDocumentToProtoConverter.toGenericDocument(
+                        documentProto, PREFIX, SCHEMA_MAP);
+        DocumentProto convertedDocumentProto =
+                GenericDocumentToProtoConverter.toDocumentProto(document);
+
+        assertThat(convertedDocumentProto).isEqualTo(documentProto);
+        assertThat(convertedGenericDocument).isEqualTo(document);
+    }
+
+    @Test
+    public void testConvertDocument_whenPropertyHasEmptyList() {
+        String emptyStringPropertyName = "emptyStringProperty";
+        DocumentProto documentProto =
+                DocumentProto.newBuilder()
+                        .setUri("uri1")
+                        .setSchema(SCHEMA_TYPE_1)
+                        .setCreationTimestampMs(5L)
+                        .setNamespace("namespace")
+                        .addProperties(
+                                PropertyProto.newBuilder().setName(emptyStringPropertyName).build())
+                        .build();
+
+        PropertyConfigProto emptyStringListProperty =
+                PropertyConfigProto.newBuilder()
+                        .setCardinality(PropertyConfigProto.Cardinality.Code.REPEATED)
+                        .setDataType(PropertyConfigProto.DataType.Code.STRING)
+                        .setPropertyName(emptyStringPropertyName)
+                        .build();
+        SchemaTypeConfigProto schemaTypeConfigProto =
+                SchemaTypeConfigProto.newBuilder()
+                        .addProperties(emptyStringListProperty)
+                        .setSchemaType(SCHEMA_TYPE_1)
+                        .build();
+        Map<String, SchemaTypeConfigProto> schemaMap =
+                ImmutableMap.of(PREFIX + SCHEMA_TYPE_1, schemaTypeConfigProto);
+
+        GenericDocument convertedDocument =
+                GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX, schemaMap);
+
+        GenericDocument expectedDocument =
+                new GenericDocument.Builder<GenericDocument.Builder<?>>(
+                                "namespace", "uri1", SCHEMA_TYPE_1)
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyString(emptyStringPropertyName)
+                        .build();
+        assertThat(convertedDocument).isEqualTo(expectedDocument);
+        assertThat(expectedDocument.getPropertyStringArray(emptyStringPropertyName)).isEmpty();
+    }
+
+    @Test
+    public void testConvertDocument_whenNestedDocumentPropertyHasEmptyList() {
+        String emptyStringPropertyName = "emptyStringProperty";
+        String documentPropertyName = "documentProperty";
+        DocumentProto nestedDocumentProto =
+                DocumentProto.newBuilder()
+                        .setUri("uri2")
+                        .setSchema(SCHEMA_TYPE_2)
+                        .setCreationTimestampMs(5L)
+                        .setNamespace("namespace")
+                        .addProperties(
+                                PropertyProto.newBuilder().setName(emptyStringPropertyName).build())
+                        .build();
+        DocumentProto documentProto =
+                DocumentProto.newBuilder()
+                        .setUri("uri1")
+                        .setSchema(SCHEMA_TYPE_1)
+                        .setCreationTimestampMs(5L)
+                        .setNamespace("namespace")
+                        .addProperties(
+                                PropertyProto.newBuilder()
+                                        .addDocumentValues(nestedDocumentProto)
+                                        .setName(documentPropertyName)
+                                        .build())
+                        .build();
+
+        PropertyConfigProto documentProperty =
+                PropertyConfigProto.newBuilder()
+                        .setCardinality(PropertyConfigProto.Cardinality.Code.REPEATED)
+                        .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
+                        .setPropertyName(documentPropertyName)
+                        .setSchemaType(SCHEMA_TYPE_2)
+                        .build();
+        SchemaTypeConfigProto schemaTypeConfigProto =
+                SchemaTypeConfigProto.newBuilder()
+                        .addProperties(documentProperty)
+                        .setSchemaType(SCHEMA_TYPE_1)
+                        .build();
+        PropertyConfigProto emptyStringListProperty =
+                PropertyConfigProto.newBuilder()
+                        .setCardinality(PropertyConfigProto.Cardinality.Code.REPEATED)
+                        .setDataType(PropertyConfigProto.DataType.Code.STRING)
+                        .setPropertyName(emptyStringPropertyName)
+                        .build();
+        SchemaTypeConfigProto nestedSchemaTypeConfigProto =
+                SchemaTypeConfigProto.newBuilder()
+                        .addProperties(emptyStringListProperty)
+                        .setSchemaType(SCHEMA_TYPE_2)
+                        .build();
+        Map<String, SchemaTypeConfigProto> schemaMap =
+                ImmutableMap.of(
+                        PREFIX + SCHEMA_TYPE_1,
+                        schemaTypeConfigProto,
+                        PREFIX + SCHEMA_TYPE_2,
+                        nestedSchemaTypeConfigProto);
+
+        GenericDocument convertedDocument =
+                GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX, schemaMap);
+
+        GenericDocument expectedDocument =
+                new GenericDocument.Builder<GenericDocument.Builder<?>>(
+                                "namespace", "uri1", SCHEMA_TYPE_1)
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyDocument(
+                                documentPropertyName,
+                                new GenericDocument.Builder<GenericDocument.Builder<?>>(
+                                                "namespace", "uri2", SCHEMA_TYPE_2)
+                                        .setCreationTimestampMillis(5L)
+                                        .setPropertyString(emptyStringPropertyName)
+                                        .build())
+                        .build();
+        assertThat(convertedDocument).isEqualTo(expectedDocument);
+        assertThat(
+                        expectedDocument
+                                .getPropertyDocument(documentPropertyName)
+                                .getPropertyStringArray(emptyStringPropertyName))
+                .isEmpty();
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java
index d07211f..26fac49 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java
@@ -21,8 +21,10 @@
 import android.app.appsearch.SearchResult;
 import android.app.appsearch.SearchResultPage;
 
+import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
 import com.android.server.appsearch.proto.DocumentProto;
 import com.android.server.appsearch.proto.PropertyProto;
+import com.android.server.appsearch.proto.SchemaTypeConfigProto;
 import com.android.server.appsearch.proto.SearchResultProto;
 import com.android.server.appsearch.proto.SnippetMatchProto;
 import com.android.server.appsearch.proto.SnippetProto;
@@ -30,20 +32,29 @@
 import org.junit.Test;
 
 import java.util.Collections;
+import java.util.Map;
 
 public class SnippetTest {
+    private static final String SCHEMA_TYPE = "schema1";
+    private static final String PACKAGE_NAME = "packageName";
+    private static final String DATABASE_NAME = "databaseName";
+    private static final String PREFIX = PrefixUtil.createPrefix(PACKAGE_NAME, DATABASE_NAME);
+    private static final SchemaTypeConfigProto SCHEMA_TYPE_CONFIG_PROTO =
+            SchemaTypeConfigProto.newBuilder().setSchemaType(PREFIX + SCHEMA_TYPE).build();
+    private static final Map<String, Map<String, SchemaTypeConfigProto>> SCHEMA_MAP =
+            Collections.singletonMap(
+                    PREFIX,
+                    Collections.singletonMap(PREFIX + SCHEMA_TYPE, SCHEMA_TYPE_CONFIG_PROTO));
 
     // TODO(tytytyww): Add tests for Double and Long Snippets.
     @Test
     public void testSingleStringSnippet() {
-
         final String propertyKeyString = "content";
         final String propertyValueString =
                 "A commonly used fake word is foo.\n"
                         + "   Another nonsense word that’s used a lot\n"
                         + "   is bar.\n";
         final String uri = "uri1";
-        final String schemaType = "schema1";
         final String searchWord = "foo";
         final String exactMatch = "foo";
         final String window = "is foo";
@@ -57,7 +68,7 @@
         DocumentProto documentProto =
                 DocumentProto.newBuilder()
                         .setUri(uri)
-                        .setSchema(schemaType)
+                        .setSchema(SCHEMA_TYPE)
                         .addProperties(property)
                         .build();
         SnippetProto snippetProto =
@@ -86,8 +97,9 @@
         SearchResultPage searchResultPage =
                 SearchResultToProtoConverter.toSearchResultPage(
                         searchResultProto,
-                        Collections.singletonList("packageName"),
-                        Collections.singletonList("databaseName"));
+                        Collections.singletonList(PACKAGE_NAME),
+                        Collections.singletonList(DATABASE_NAME),
+                        SCHEMA_MAP);
         for (SearchResult result : searchResultPage.getResults()) {
             SearchResult.MatchInfo match = result.getMatches().get(0);
             assertThat(match.getPropertyPath()).isEqualTo(propertyKeyString);
@@ -112,7 +124,6 @@
                         + "   Another nonsense word that’s used a lot\n"
                         + "   is bar.\n";
         final String uri = "uri1";
-        final String schemaType = "schema1";
         final String searchWord = "foo";
         final String exactMatch = "foo";
         final String window = "is foo";
@@ -126,7 +137,7 @@
         DocumentProto documentProto =
                 DocumentProto.newBuilder()
                         .setUri(uri)
-                        .setSchema(schemaType)
+                        .setSchema(SCHEMA_TYPE)
                         .addProperties(property)
                         .build();
         SearchResultProto.ResultProto resultProto =
@@ -137,8 +148,9 @@
         SearchResultPage searchResultPage =
                 SearchResultToProtoConverter.toSearchResultPage(
                         searchResultProto,
-                        Collections.singletonList("packageName"),
-                        Collections.singletonList("databaseName"));
+                        Collections.singletonList(PACKAGE_NAME),
+                        Collections.singletonList(DATABASE_NAME),
+                        SCHEMA_MAP);
         for (SearchResult result : searchResultPage.getResults()) {
             assertThat(result.getMatches()).isEmpty();
         }
@@ -162,7 +174,7 @@
         DocumentProto documentProto =
                 DocumentProto.newBuilder()
                         .setUri("uri1")
-                        .setSchema("schema1")
+                        .setSchema(SCHEMA_TYPE)
                         .addProperties(property1)
                         .addProperties(property2)
                         .build();
@@ -203,8 +215,9 @@
         SearchResultPage searchResultPage =
                 SearchResultToProtoConverter.toSearchResultPage(
                         searchResultProto,
-                        Collections.singletonList("packageName"),
-                        Collections.singletonList("databaseName"));
+                        Collections.singletonList(PACKAGE_NAME),
+                        Collections.singletonList(DATABASE_NAME),
+                        SCHEMA_MAP);
         for (SearchResult result : searchResultPage.getResults()) {
 
             SearchResult.MatchInfo match1 = result.getMatches().get(0);
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
index 79a5ed6..5c53d43 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -98,8 +98,9 @@
         Log.i(TAG, "starting testPostA2dpDeviceConnectionChange");
         Assert.assertNotNull("invalid null BT device", mFakeBtDevice);
 
-        mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
-                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1);
+        mAudioDeviceBroker.queueBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+                new AudioDeviceBroker.BtDeviceConnectionInfo(mFakeBtDevice,
+                        BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1));
         Thread.sleep(2 * MAX_MESSAGE_HANDLING_DELAY_MS);
         verify(mSpyDevInventory, times(1)).setBluetoothA2dpDeviceConnectionState(
                 any(BluetoothDevice.class),
@@ -209,20 +210,23 @@
         ((NoOpAudioSystemAdapter) mSpyAudioSystem).configureIsStreamActive(mockMediaPlayback);
 
         // first connection: ensure the device is connected as a starting condition for the test
-        mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
-                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1);
+        mAudioDeviceBroker.queueBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+                new AudioDeviceBroker.BtDeviceConnectionInfo(mFakeBtDevice,
+                        BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1));
         Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
 
         // disconnection
-        mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
-                BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.A2DP, false, -1);
+        mAudioDeviceBroker.queueBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+                new AudioDeviceBroker.BtDeviceConnectionInfo(mFakeBtDevice,
+                        BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.A2DP, false, -1));
         if (delayAfterDisconnection > 0) {
             Thread.sleep(delayAfterDisconnection);
         }
 
         // reconnection
-        mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
-                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 2);
+        mAudioDeviceBroker.queueBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+                new AudioDeviceBroker.BtDeviceConnectionInfo(mFakeBtDevice,
+                        BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 2));
         Thread.sleep(AudioService.BECOMING_NOISY_DELAY_MS + MAX_MESSAGE_HANDLING_DELAY_MS);
 
         // Verify disconnection has been cancelled and we're seeing two connections attempts,
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 73ec5b8..1b42dfa 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -31,7 +31,7 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
 import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
-import static android.app.admin.PasswordMetrics.computeForPassword;
+import static android.app.admin.PasswordMetrics.computeForPasswordOrPin;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
 import static android.net.InetAddresses.parseNumericAddress;
 
@@ -1551,6 +1551,16 @@
     @Test
     public void testSetProfileOwner_failures() throws Exception {
         // TODO Test more failure cases.  Basically test all chacks in enforceCanSetProfileOwner().
+        // Package doesn't exist and caller is not system
+        assertExpectException(SecurityException.class,
+                /* messageRegex= */ "Calling identity is not authorized",
+                () -> dpm.setProfileOwner(admin1, "owner-name", UserHandle.USER_SYSTEM));
+
+        // Package exists, but caller is not system
+        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+        assertExpectException(SecurityException.class,
+                /* messageRegex= */ "Calling identity is not authorized",
+                () -> dpm.setProfileOwner(admin1, "owner-name", UserHandle.USER_SYSTEM));
     }
 
     @Test
@@ -3169,6 +3179,16 @@
     }
 
     @Test
+    public void testSetUserProvisioningState_profileFinalized_canTransitionToUserUnmanaged()
+            throws Exception {
+        setupProfileOwner();
+
+        exerciseUserProvisioningTransitions(CALLER_USER_HANDLE,
+                DevicePolicyManager.STATE_USER_PROFILE_FINALIZED,
+                DevicePolicyManager.STATE_USER_UNMANAGED);
+    }
+
+    @Test
     public void testSetUserProvisioningState_illegalTransitionToAnotherInProgressState()
             throws Exception {
         setupProfileOwner();
@@ -5156,7 +5176,8 @@
 
         reset(mContext.spiedContext);
 
-        PasswordMetrics passwordMetricsNoSymbols = computeForPassword("abcdXYZ5".getBytes());
+        PasswordMetrics passwordMetricsNoSymbols = computeForPasswordOrPin(
+                "abcdXYZ5".getBytes(), /* isPin */ false);
 
         setActivePasswordState(passwordMetricsNoSymbols);
         assertThat(dpm.isActivePasswordSufficient()).isTrue();
@@ -5183,7 +5204,8 @@
         reset(mContext.spiedContext);
         assertThat(dpm.isActivePasswordSufficient()).isFalse();
 
-        PasswordMetrics passwordMetricsWithSymbols = computeForPassword("abcd.XY5".getBytes());
+        PasswordMetrics passwordMetricsWithSymbols = computeForPasswordOrPin(
+                "abcd.XY5".getBytes(), /* isPin */ false);
 
         setActivePasswordState(passwordMetricsWithSymbols);
         assertThat(dpm.isActivePasswordSufficient()).isTrue();
@@ -5237,7 +5259,7 @@
         parentDpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM);
 
         when(getServices().lockSettingsInternal.getUserPasswordMetrics(UserHandle.USER_SYSTEM))
-                .thenReturn(computeForPassword("184342".getBytes()));
+                .thenReturn(computeForPasswordOrPin("184342".getBytes(), /* isPin */ true));
 
         // Numeric password is compliant with current requirement (QUALITY_NUMERIC set explicitly
         // on the parent admin)
@@ -6360,7 +6382,7 @@
                 .thenReturn(CALLER_USER_HANDLE);
         when(getServices().lockSettingsInternal
                 .getUserPasswordMetrics(CALLER_USER_HANDLE))
-                .thenReturn(computeForPassword("asdf".getBytes()));
+                .thenReturn(computeForPasswordOrPin("asdf".getBytes(), /* isPin */ false));
 
         assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_MEDIUM);
     }
@@ -6380,10 +6402,10 @@
 
         when(getServices().lockSettingsInternal
                 .getUserPasswordMetrics(CALLER_USER_HANDLE))
-                .thenReturn(computeForPassword("asdf".getBytes()));
+                .thenReturn(computeForPasswordOrPin("asdf".getBytes(), /* isPin */ false));
         when(getServices().lockSettingsInternal
                 .getUserPasswordMetrics(parentUser.id))
-                .thenReturn(computeForPassword("parentUser".getBytes()));
+                .thenReturn(computeForPasswordOrPin("parentUser".getBytes(), /* isPin */ false));
 
         assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_HIGH);
     }
@@ -7059,13 +7081,15 @@
         assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_NONE);
 
         reset(mContext.spiedContext);
-        PasswordMetrics passwordMetricsNoSymbols = computeForPassword("1234".getBytes());
+        PasswordMetrics passwordMetricsNoSymbols = computeForPasswordOrPin(
+                "1234".getBytes(), /* isPin */ true);
         setActivePasswordState(passwordMetricsNoSymbols);
         assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_LOW);
         assertThat(dpm.isActivePasswordSufficient()).isFalse();
 
         reset(mContext.spiedContext);
-        passwordMetricsNoSymbols = computeForPassword("84125312943a".getBytes());
+        passwordMetricsNoSymbols = computeForPasswordOrPin(
+                "84125312943a".getBytes(), /* isPin */ false);
         setActivePasswordState(passwordMetricsNoSymbols);
         assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_HIGH);
         // using isActivePasswordSufficient
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
index d888b92..876c845 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
@@ -37,7 +37,7 @@
 
         @JvmStatic
         @Parameterized.Parameters(name = "deferRebuild {0}")
-        fun parameters() = arrayOf(true, false)
+        fun parameters() = arrayOf(/*true, */false)
     }
 
     private lateinit var mapper: OverlayReferenceMapper
@@ -55,11 +55,17 @@
     fun targetWithOverlay() {
         val target = mockTarget()
         val overlay = mockOverlay()
-        val existing = mapper.addInOrder(overlay)
+        val existing = mapper.addInOrder(overlay) {
+            assertThat(it).isEmpty()
+        }
         assertEmpty()
-        mapper.addInOrder(target, existing = existing)
+        mapper.addInOrder(target, existing = existing) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay))
-        mapper.remove(target)
+        mapper.remove(target) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertEmpty()
     }
 
@@ -78,22 +84,34 @@
                         )
                 )
         )
-        val existing = mapper.addInOrder(overlay0, overlay1)
+        val existing = mapper.addInOrder(overlay0, overlay1) {
+            assertThat(it).isEmpty()
+        }
         assertEmpty()
-        mapper.addInOrder(target, existing = existing)
+        mapper.addInOrder(target, existing = existing) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay0, overlay1))
-        mapper.remove(overlay0)
+        mapper.remove(overlay0) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay1))
-        mapper.remove(target)
+        mapper.remove(target) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertEmpty()
     }
 
     @Test
     fun targetWithoutOverlay() {
         val target = mockTarget()
-        mapper.addInOrder(target)
+        mapper.addInOrder(target) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
-        mapper.remove(target)
+        mapper.remove(target) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertEmpty()
     }
 
@@ -101,11 +119,17 @@
     fun overlayWithTarget() {
         val target = mockTarget()
         val overlay = mockOverlay()
-        val existing = mapper.addInOrder(target)
+        val existing = mapper.addInOrder(target) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
-        mapper.addInOrder(overlay, existing = existing)
+        mapper.addInOrder(overlay, existing = existing) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay))
-        mapper.remove(overlay)
+        mapper.remove(overlay) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
     }
 
@@ -122,34 +146,52 @@
                         )
                 )
         )
-        mapper.addInOrder(target0, target1, overlay)
+        mapper.addInOrder(target0, target1, overlay) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertMapping(ACTOR_PACKAGE_NAME to setOf(target0, target1, overlay))
-        mapper.remove(target0)
+        mapper.remove(target0) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertMapping(ACTOR_PACKAGE_NAME to setOf(target1, overlay))
-        mapper.remove(target1)
+        mapper.remove(target1) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertEmpty()
     }
 
     @Test
     fun overlayWithoutTarget() {
         val overlay = mockOverlay()
-        mapper.addInOrder(overlay)
+        mapper.addInOrder(overlay) {
+            assertThat(it).isEmpty()
+        }
         // An overlay can only have visibility exposed through its target
         assertEmpty()
-        mapper.remove(overlay)
+        mapper.remove(overlay) {
+            assertThat(it).isEmpty()
+        }
         assertEmpty()
     }
 
     private fun OverlayReferenceMapper.addInOrder(
         vararg pkgs: AndroidPackage,
-        existing: MutableMap<String, AndroidPackage> = mutableMapOf()
-    ) = pkgs.fold(existing) { map, pkg ->
-        addPkg(pkg, map)
-        map[pkg.packageName] = pkg
-        return@fold map
+        existing: MutableMap<String, AndroidPackage> = mutableMapOf(),
+        assertion: (changedPackages: Set<String>) -> Unit
+    ): MutableMap<String, AndroidPackage> {
+        val changedPackages = mutableSetOf<String>()
+        pkgs.forEach {
+            changedPackages += addPkg(it, existing)
+            existing[it.packageName] = it
+        }
+        assertion(changedPackages)
+        return existing
     }
 
-    private fun OverlayReferenceMapper.remove(pkg: AndroidPackage) = removePkg(pkg.packageName)
+    private fun OverlayReferenceMapper.remove(
+        pkg: AndroidPackage,
+        assertion: (changedPackages: Set<String>) -> Unit
+    ) = assertion(removePkg(pkg.packageName))
 
     private fun assertMapping(vararg pairs: Pair<String, Set<AndroidPackage>>) {
         val expected = pairs.associate { it }
diff --git a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
index 8070bd1..558e259 100644
--- a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
@@ -7,6 +7,14 @@
           "include-filter": "com.android.server.om."
         }
       ]
+    },
+    {
+      "name": "PackageManagerServiceHostTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.pm.test.OverlayActorVisibilityTest"
+        }
+      ]
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
index 9f428c7..67dd055 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
@@ -799,10 +799,18 @@
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
-        PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_APPID);
+        // Packages must be added in actor -> overlay -> target order so that the implicit
+        // visibility of the actor into the overlay can be tested
+
+        PackageSetting actorSetting = simulateAddPackage(appsFilter, actor, DUMMY_ACTOR_APPID);
         PackageSetting overlaySetting =
                 simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_APPID);
-        PackageSetting actorSetting = simulateAddPackage(appsFilter, actor, DUMMY_ACTOR_APPID);
+
+        // Actor can not see overlay (yet)
+        assertTrue(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting,
+                overlaySetting, SYSTEM_USER));
+
+        PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_APPID);
 
         // Actor can see both target and overlay
         assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting,
@@ -821,6 +829,12 @@
                 actorSetting, SYSTEM_USER));
         assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_APPID, overlaySetting,
                 actorSetting, SYSTEM_USER));
+
+        appsFilter.removePackage(targetSetting);
+
+        // Actor loses visibility to the overlay via removal of the target
+        assertTrue(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting,
+                overlaySetting, SYSTEM_USER));
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
index 809b6d5..182848b4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
@@ -123,6 +123,7 @@
         for (long i = cal.getTimeInMillis(); i >= 5; i--) {
             File file = mock(File.class);
             when(file.getName()).thenReturn(String.valueOf(i));
+            when(file.getAbsolutePath()).thenReturn(String.valueOf(i));
             AtomicFile af = new AtomicFile(file);
             expectedFiles.add(af);
             mDataBase.mHistoryFiles.addLast(af);
@@ -133,6 +134,7 @@
         for (int i = 5; i >= 0; i--) {
             File file = mock(File.class);
             when(file.getName()).thenReturn(String.valueOf(cal.getTimeInMillis() - i));
+            when(file.getAbsolutePath()).thenReturn(String.valueOf(cal.getTimeInMillis() - i));
             AtomicFile af = new AtomicFile(file);
             mDataBase.mHistoryFiles.addLast(af);
         }
@@ -158,6 +160,7 @@
         for (long i = cal.getTimeInMillis(); i >= 5; i--) {
             File file = mock(File.class);
             when(file.getName()).thenReturn(i + ".bak");
+            when(file.getAbsolutePath()).thenReturn(i + ".bak");
             AtomicFile af = new AtomicFile(file);
             mDataBase.mHistoryFiles.addLast(af);
         }
@@ -415,4 +418,36 @@
         assertThat(mDataBase.mBuffer).isNotEqualTo(nh);
         verify(mAlarmManager, times(1)).setExactAndAllowWhileIdle(anyInt(), anyLong(), any());
     }
+
+    @Test
+    public void testRemoveFilePathFromHistory_hasMatch() throws Exception {
+        for (int i = 0; i < 5; i++) {
+            AtomicFile af = mock(AtomicFile.class);
+            when(af.getBaseFile()).thenReturn(new File(mRootDir, "af" + i));
+            mDataBase.mHistoryFiles.addLast(af);
+        }
+        // Baseline size of history files
+        assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(5);
+
+        // Remove only file number 3
+        String filePathToRemove = new File(mRootDir, "af3").getAbsolutePath();
+        mDataBase.removeFilePathFromHistory(filePathToRemove);
+        assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(4);
+    }
+
+    @Test
+    public void testRemoveFilePathFromHistory_noMatch() throws Exception {
+        for (int i = 0; i < 5; i++) {
+            AtomicFile af = mock(AtomicFile.class);
+            when(af.getBaseFile()).thenReturn(new File(mRootDir, "af" + i));
+            mDataBase.mHistoryFiles.addLast(af);
+        }
+        // Baseline size of history files
+        assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(5);
+
+        // Attempt to remove a filename that doesn't exist, expect nothing to break or change
+        String filePathToRemove = new File(mRootDir, "af.thisfileisfake").getAbsolutePath();
+        mDataBase.removeFilePathFromHistory(filePathToRemove);
+        assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(5);
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 87efaa2..37d7198 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -5057,7 +5057,8 @@
     }
 
     @Test
-    public void testToastRateLimiterCanPreventShowCallForCustomToast() throws Exception {
+    public void testToastRateLimiterWontPreventShowCallForCustomToastWhenInForeground()
+            throws Exception {
         final String testPackage = "testPackageName";
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = false;
@@ -5075,30 +5076,7 @@
         INotificationManager nmService = (INotificationManager) mService.mService;
 
         nmService.enqueueToast(testPackage, token, callback, 2000, 0);
-        verify(callback, times(0)).show(any());
-    }
-
-    @Test
-    public void testCustomToastRateLimiterAllowsLimitAvoidanceWithPermission() throws Exception {
-        final String testPackage = "testPackageName";
-        assertEquals(0, mService.mToastQueue.size());
-        mService.isSystemUid = false;
-        setToastRateIsWithinQuota(false); // rate limit reached
-        // Avoids rate limiting.
-        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, true);
-
-        // package is not suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
-                .thenReturn(false);
-
-        setAppInForegroundForToasts(mUid, true);
-
-        Binder token = new Binder();
-        ITransientNotification callback = mock(ITransientNotification.class);
-        INotificationManager nmService = (INotificationManager) mService.mService;
-
-        nmService.enqueueToast(testPackage, token, callback, 2000, 0);
-        verify(callback).show(any());
+        verify(callback, times(1)).show(any());
     }
 
     @Test
@@ -5206,12 +5184,14 @@
     }
 
     @Test
-    public void testToastRateLimiterCanPreventShowCallForTextToast() throws Exception {
+    public void testToastRateLimiterCanPreventShowCallForTextToast_whenInBackground()
+            throws Exception {
         final String testPackage = "testPackageName";
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = false;
         setToastRateIsWithinQuota(false); // rate limit reached
         setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
+        setAppInForegroundForToasts(mUid, false);
 
         // package is not suspended
         when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5226,12 +5206,35 @@
     }
 
     @Test
+    public void testToastRateLimiterWontPreventShowCallForTextToast_whenInForeground()
+            throws Exception {
+        final String testPackage = "testPackageName";
+        assertEquals(0, mService.mToastQueue.size());
+        mService.isSystemUid = false;
+        setToastRateIsWithinQuota(false); // rate limit reached
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
+        setAppInForegroundForToasts(mUid, true);
+
+        // package is not suspended
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+                .thenReturn(false);
+
+        Binder token = new Binder();
+        INotificationManager nmService = (INotificationManager) mService.mService;
+
+        nmService.enqueueTextToast(testPackage, token, "Text", 2000, 0, null);
+        verify(mStatusBar, times(1))
+                .showToast(anyInt(), any(), any(), any(), any(), anyInt(), any());
+    }
+
+    @Test
     public void testTextToastRateLimiterAllowsLimitAvoidanceWithPermission() throws Exception {
         final String testPackage = "testPackageName";
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = false;
         setToastRateIsWithinQuota(false); // rate limit reached
         setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, true);
+        setAppInForegroundForToasts(mUid, false);
 
         // package is not suspended
         when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 7c2cfab..ee1d393 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1515,6 +1515,129 @@
         assertEquals(primarySplitBounds, letterboxedBounds);
     }
 
+    @Test
+    public void testUpdateResolvedBoundsHorizontalPosition_left() {
+        // Display configured as (2800, 1400).
+        assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
+                /* letterboxHorizontalPositionMultiplier */ 0.0f,
+                // At launch.
+                /* fixedOrientationLetterbox */ new Rect(0, 0, 700, 1400),
+                // After 90 degree rotation.
+                /* sizeCompatUnscaled */ new Rect(0, 0, 700, 1400),
+                // After the display is resized to (700, 1400).
+                /* sizeCompatScaled */ new Rect(0, 0, 350, 700));
+    }
+
+    @Test
+    public void testUpdateResolvedBoundsHorizontalPosition_center() {
+        // Display configured as (2800, 1400).
+        assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
+                /* letterboxHorizontalPositionMultiplier */ 0.5f,
+                // At launch.
+                /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
+                // After 90 degree rotation.
+                /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
+                // After the display is resized to (700, 1400).
+                /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
+    }
+
+    @Test
+    public void testUpdateResolvedBoundsHorizontalPosition_invalidMultiplier_defaultToCenter() {
+        // Display configured as (2800, 1400).
+
+        // Below 0.0.
+        assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
+                /* letterboxHorizontalPositionMultiplier */ -1.0f,
+                // At launch.
+                /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
+                // After 90 degree rotation.
+                /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
+                // After the display is resized to (700, 1400).
+                /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
+
+        // Above 1.0
+        assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
+                /* letterboxHorizontalPositionMultiplier */ 2.0f,
+                // At launch.
+                /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
+                // After 90 degree rotation.
+                /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
+                // After the display is resized to (700, 1400).
+                /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
+    }
+
+    @Test
+    public void testUpdateResolvedBoundsHorizontalPosition_right() {
+        // Display configured as (2800, 1400).
+        assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
+                /* letterboxHorizontalPositionMultiplier */ 1.0f,
+                // At launch.
+                /* fixedOrientationLetterbox */ new Rect(2100, 0, 2800, 1400),
+                // After 90 degree rotation.
+                /* sizeCompatUnscaled */ new Rect(700, 0, 1400, 1400),
+                // After the display is resized to (700, 1400).
+                /* sizeCompatScaled */ new Rect(1050, 0, 1400, 700));
+    }
+
+    private void assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
+            float letterboxHorizontalPositionMultiplier, Rect fixedOrientationLetterbox,
+            Rect sizeCompatUnscaled, Rect sizeCompatScaled) {
+        // Set up a display in landscape and ignoring orientation request.
+        setUpDisplaySizeWithApp(2800, 1400);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+        mActivity.mWmService.setLetterboxHorizontalPositionMultiplier(
+                letterboxHorizontalPositionMultiplier);
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+        assertEquals(fixedOrientationLetterbox, mActivity.getBounds());
+
+        // Rotate to put activity in size compat mode.
+        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+        assertTrue(mActivity.inSizeCompatMode());
+        // Activity is in size compat mode but not scaled.
+        assertEquals(sizeCompatUnscaled, mActivity.getBounds());
+
+        // Force activity to scaled down for size compat mode.
+        resizeDisplay(mTask.mDisplayContent, 700, 1400);
+
+        assertTrue(mActivity.inSizeCompatMode());
+        assertScaled();
+        assertEquals(sizeCompatScaled, mActivity.getBounds());
+    }
+
+    @Test
+    public void testUpdateResolvedBoundsHorizontalPosition_activityFillParentWidth() {
+        // When activity width equals parent width, multiplier shouldn't have any effect.
+        assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
+                /* letterboxHorizontalPositionMultiplier */ 0.0f);
+        assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
+                /* letterboxHorizontalPositionMultiplier */ 0.5f);
+        assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
+                /* letterboxHorizontalPositionMultiplier */ 1.0f);
+    }
+
+    private void assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
+            float letterboxHorizontalPositionMultiplier) {
+        // Set up a display in landscape and ignoring orientation request.
+        setUpDisplaySizeWithApp(2800, 1400);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+        mActivity.mWmService.setLetterboxHorizontalPositionMultiplier(
+                letterboxHorizontalPositionMultiplier);
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+
+        assertFitted();
+
+        // Rotate to put activity in size compat mode.
+        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+        assertTrue(mActivity.inSizeCompatMode());
+        // Activity is in size compat mode but not scaled.
+        assertEquals(new Rect(0, 0, 1400, 700), mActivity.getBounds());
+    }
+
     private static WindowState addWindowToActivity(ActivityRecord activity) {
         final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
         params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
index f3616da6c..619aee6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -17,6 +17,8 @@
 package com.android.server.wm;
 
 import android.annotation.NonNull;
+import android.graphics.ColorSpace;
+import android.graphics.GraphicBuffer;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -253,4 +255,14 @@
     public SurfaceControl.Transaction unsetFixedTransformHint(@NonNull SurfaceControl sc) {
         return this;
     }
+
+    @Override
+    public SurfaceControl.Transaction setBuffer(SurfaceControl sc, GraphicBuffer buffer) {
+        return this;
+    }
+
+    @Override
+    public SurfaceControl.Transaction setColorSpace(SurfaceControl sc, ColorSpace colorSpace) {
+        return this;
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index dd0c9e6..d9aa871 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -23,6 +23,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
@@ -50,7 +51,13 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.view.IWindowSessionCallback;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.View;
+import android.view.WindowManager;
 
 import androidx.test.filters.SmallTest;
 
@@ -250,4 +257,31 @@
                 eq(clientToken), eq(windowToken), anyInt(), eq(TYPE_INPUT_METHOD),
                 eq(windowToken.mOptions));
     }
+
+    @Test
+    public void testAddWindowWithSubWindowTypeByWindowContext() {
+        spyOn(mWm.mWindowContextListenerController);
+
+        final WindowToken windowToken = createTestWindowToken(TYPE_INPUT_METHOD, mDefaultDisplay);
+        final Session session = new Session(mWm, new IWindowSessionCallback.Stub() {
+            @Override
+            public void onAnimatorScaleChanged(float v) throws RemoteException {}
+        });
+        final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                TYPE_APPLICATION_ATTACHED_DIALOG);
+        params.token = windowToken.token;
+        final IBinder windowContextToken = new Binder();
+        params.setWindowContextToken(windowContextToken);
+        doReturn(true).when(mWm.mWindowContextListenerController)
+                .hasListener(eq(windowContextToken));
+        doReturn(TYPE_INPUT_METHOD).when(mWm.mWindowContextListenerController)
+                .getWindowType(eq(windowContextToken));
+
+        mWm.addWindow(session, new TestIWindow(), params, View.VISIBLE, DEFAULT_DISPLAY,
+                UserHandle.USER_SYSTEM, new InsetsState(), null, new InsetsState(),
+                new InsetsSourceControl[0]);
+
+        verify(mWm.mWindowContextListenerController, never()).registerWindowContainerListener(any(),
+                any(), anyInt(), anyInt(), any());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 5bafbbd..ae12062 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -101,6 +101,7 @@
 import com.android.internal.policy.AttributeCache;
 import com.android.internal.util.ArrayUtils;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.runner.Description;
@@ -208,10 +209,25 @@
         // {@link com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio}, is set
         // on some device form factors.
         mAtm.mWindowManager.setFixedOrientationLetterboxAspectRatio(0);
+        // Ensure letterbox position multiplier is not overridden on any device target.
+        // {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier},
+        // may be set on some device form factors.
+        mAtm.mWindowManager.setLetterboxHorizontalPositionMultiplier(0.5f);
 
         checkDeviceSpecificOverridesNotApplied();
     }
 
+    @After
+    public void tearDown() throws Exception {
+        // Revert back to device overrides.
+        mAtm.mWindowManager.setFixedOrientationLetterboxAspectRatio(
+                mContext.getResources().getFloat(
+                        com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio));
+        mAtm.mWindowManager.setLetterboxHorizontalPositionMultiplier(
+                mContext.getResources().getFloat(
+                    com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier));
+    }
+
     /**
      * Check that device-specific overrides are not applied. Only need to check once during entire
      * test run for each case: global overrides, default display, and test display.
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 2e692e6..7f24c36 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -2139,6 +2139,12 @@
                 Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", "
                         + "forceRestart=" + forceRestart);
             }
+            if (mCurrentGadgetHalVersion < UsbManager.GADGET_HAL_V1_2) {
+                if ((functions & UsbManager.FUNCTION_NCM) != 0) {
+                    Slog.e(TAG, "Could not set unsupported function for the GadgetHal");
+                    return;
+                }
+            }
             if (mCurrentFunctions != functions
                     || !mCurrentFunctionsApplied
                     || forceRestart) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index bcfb302..6f701f7 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -434,7 +434,8 @@
         }
         try {
             AudioRecord audioRecord = new AudioRecord(
-                    new AudioAttributes.Builder().setHotwordModeEnabled(true).build(),
+                    new AudioAttributes.Builder()
+                            .setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD).build(),
                     audioFormat,
                     getBufferSizeInBytes(
                             audioFormat.getSampleRate(),
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index c8ed82c..0539897 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -1055,6 +1055,20 @@
      */
     public static final int HANDOVER_FAILED = 0x10006;
 
+    /**
+     * Enterprise setup failure: duplicate CID in DataCallResponse.
+     *
+     * @hide
+     */
+    public static final int DUPLICATE_CID = 0x10007;
+
+    /**
+     * Enterprise setup failure: no default data connection set up yet.
+     *
+     * @hide
+     */
+    public static final int NO_DEFAULT_DATA = 0x10008;
+
     private static final Map<Integer, String> sFailCauseMap;
     static {
         sFailCauseMap = new HashMap<>();
@@ -1481,6 +1495,9 @@
         sFailCauseMap.put(UNACCEPTABLE_NETWORK_PARAMETER,
                 "UNACCEPTABLE_NETWORK_PARAMETER");
         sFailCauseMap.put(LOST_CONNECTION, "LOST_CONNECTION");
+        sFailCauseMap.put(HANDOVER_FAILED, "HANDOVER_FAILED");
+        sFailCauseMap.put(DUPLICATE_CID, "DUPLICATE_CID");
+        sFailCauseMap.put(NO_DEFAULT_DATA, "NO_DEFAULT_DATA");
     }
 
     private DataFailCause() {
@@ -1580,6 +1597,7 @@
                             add(RADIO_NOT_AVAILABLE);
                             add(UNACCEPTABLE_NETWORK_PARAMETER);
                             add(SIGNAL_LOST);
+                            add(DUPLICATE_CID);
                         }
                     };
                 }
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 0bb0337..642b19e 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -527,6 +527,33 @@
     }
 
     @Test
+    public void testExpireSession_Phase1_Install() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        Install.single(TestApp.A1).commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        Install.single(TestApp.A2).setEnableRollback().setStaged().commit();
+    }
+
+    @Test
+    public void testExpireSession_Phase2_VerifyInstall() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+                rm.getAvailableRollbacks(), TestApp.A);
+        assertThat(rollback).isNotNull();
+        assertThat(rollback).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1));
+        assertThat(rollback.isStaged()).isTrue();
+    }
+
+    @Test
+    public void testExpireSession_Phase3_VerifyRollback() throws Exception {
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+                rm.getAvailableRollbacks(), TestApp.A);
+        assertThat(rollback).isNotNull();
+    }
+
+    @Test
     public void hasMainlineModule() throws Exception {
         String pkgName = getModuleMetadataPackageName();
         boolean existed =  InstrumentationRegistry.getInstrumentation().getContext()
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 304567a3..1aa5c24 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -40,7 +40,9 @@
 import org.junit.runner.RunWith;
 
 import java.io.File;
+import java.time.Instant;
 import java.util.Collections;
+import java.util.Date;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -582,6 +584,27 @@
         runPhase("testWatchdogMonitorsAcrossReboots_Phase3_VerifyRollback");
     }
 
+    /**
+     * Tests an available rollback shouldn't be deleted when its session expires.
+     */
+    @Test
+    public void testExpireSession() throws Exception {
+        runPhase("testExpireSession_Phase1_Install");
+        getDevice().reboot();
+        runPhase("testExpireSession_Phase2_VerifyInstall");
+
+        // Advance system clock by 7 days to expire the staged session
+        Instant t1 = Instant.ofEpochMilli(getDevice().getDeviceDate());
+        Instant t2 = t1.plusMillis(TimeUnit.DAYS.toMillis(7));
+        runAsRoot(() -> getDevice().setDate(Date.from(t2)));
+
+        // Somehow we need to wait for a while before reboot. Otherwise the change to the
+        // system clock will be reset after reboot.
+        Thread.sleep(3000);
+        getDevice().reboot();
+        runPhase("testExpireSession_Phase3_VerifyRollback");
+    }
+
     private void pushTestApex() throws Exception {
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
         final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex";
diff --git a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
index 454d5b5..2b45b3d 100644
--- a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
+++ b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
@@ -44,6 +44,9 @@
             setSubscriberId("MySubId")
             setPartialConnectivityAcceptable(false)
             setUnvalidatedConnectivityAcceptable(true)
+            if (isAtLeastS()) {
+                setBypassableVpn(true)
+            }
         }.build()
         if (isAtLeastS()) {
             // From S, the config will have 12 items
@@ -66,6 +69,7 @@
             if (isAtLeastS()) {
                 setNat64DetectionEnabled(false)
                 setProvisioningNotificationEnabled(false)
+                setBypassableVpn(true)
             }
         }.build()
 
@@ -78,6 +82,7 @@
         if (isAtLeastS()) {
             assertFalse(config.isNat64DetectionEnabled())
             assertFalse(config.isProvisioningNotificationEnabled())
+            assertTrue(config.isBypassableVpn())
         } else {
             assertTrue(config.isNat64DetectionEnabled())
             assertTrue(config.isProvisioningNotificationEnabled())
diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java
index 6cbdd25..19f8843 100644
--- a/tests/net/java/android/net/ConnectivityManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -384,7 +384,7 @@
                 eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
                 eq(testPkgName), eq(testAttributionTag));
 
-        manager.requestBackgroundNetwork(request, handler, callback);
+        manager.requestBackgroundNetwork(request, callback, handler);
         verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities),
                 eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
                 eq(testPkgName), eq(testAttributionTag));
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 1b0a500..039ce2f 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -4313,7 +4313,7 @@
         final TestNetworkCallback cellBgCallback = new TestNetworkCallback();
         mCm.requestBackgroundNetwork(new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_CELLULAR).build(),
-                mCsHandlerThread.getThreadHandler(), cellBgCallback);
+                cellBgCallback, mCsHandlerThread.getThreadHandler());
 
         // Make callbacks for monitoring.
         final NetworkRequest request = new NetworkRequest.Builder().build();
diff --git a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
index b592000..0c7363e 100644
--- a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
@@ -153,21 +153,19 @@
         verify(mConnectivityManager)
                 .requestBackgroundNetwork(
                         eq(getWifiRequest(expectedSubIds)),
-                        any(),
-                        any(NetworkBringupCallback.class));
+                        any(NetworkBringupCallback.class),
+                        any());
         for (final int subId : expectedSubIds) {
             verify(mConnectivityManager)
                     .requestBackgroundNetwork(
                             eq(getCellRequestForSubId(subId)),
-                            any(),
-                            any(NetworkBringupCallback.class));
+                            any(NetworkBringupCallback.class), any());
         }
 
         verify(mConnectivityManager)
                 .requestBackgroundNetwork(
                         eq(getRouteSelectionRequest(expectedSubIds)),
-                        any(),
-                        any(RouteSelectionCallback.class));
+                        any(RouteSelectionCallback.class), any());
     }
 
     @Test
@@ -267,8 +265,8 @@
         verify(mConnectivityManager)
                 .requestBackgroundNetwork(
                         eq(getRouteSelectionRequest(INITIAL_SUB_IDS)),
-                        any(),
-                        mRouteSelectionCallbackCaptor.capture());
+                        mRouteSelectionCallbackCaptor.capture(),
+                        any());
 
         RouteSelectionCallback cb = mRouteSelectionCallbackCaptor.getValue();
         cb.onAvailable(mNetwork);